/grace-style-guide

Coding Style Guide for GSA GRACE Project

OtherNOASSERTION

GRACE Style Guide

Index

  1. Golang
  2. Terraform
  3. References for Terraform Style Guides
  4. AWS Resource Naming
  5. Public domain

Golang

Linter

GRACE was using the gometalinter to enforce community recommended style guidelines on Go code. However, the gometalinter project has been deprecated in favor of golangci-lint and GRACE will be migrating to this linter instead.

Testing

  1. Favor using table driven tests (See Figure 1).
  2. Keep tests focused. Each test case should test a single behavior and be named accordingly.
  3. Use assertions instead of if statements, where possible (See Figure 2).
  4. Use sub-tests to differentiate error messages when using assertions. This will make debugging easier.
  5. Test cases should cover both the Happy path and the Unhappy path (See Happy Path vs Unhappy Path).

Mocking

  1. Use native go interfaces when feasible (See Figure 3).
  2. Use GoMock when go interfaces alone are not sufficient (See Figure 4).

Code Coverage

Well written code will generally have line, branch and mutation coverage higher than 80%. Prioritize testing code that is most likely to change in the future.

Figure 1

// Sum ... returns the sum of two numbers
func Sum(a, b int) int {
	return a + b
}

func TestSum(t *testing.T) {
    tt := map[string]struct {
        A        int
        B        int
        Expected int
    }{
        "1+2=3": {1, 2, 3},
        "2+2=4": {2, 2, 4},
    }
    for name, tc := range tt {
        tc := tc
        t.Run(name, func(t *testing.T) {
            actual := Sum(tc.A, tc.B)
            assert.Equal(t, tc.Expected, actual)
        })
    }
}

[top] [back]

Figure 2

// Read ... reads the file at 'path' and returns the contents as a string
func Read(path string) (string, error) {
	byt, err := ioutil.ReadFile(path)
	if err != nil {
		return "", err
	}
	return string(byt), nil
}

func TestRead(t *testing.T) {
	tt := map[string]struct {
		Path     string
		Expected string
		Error    bool
	}{
		"File":    {Path: "localfile.json", Expected: `{"data":"this"}`},
		"FileErr": {Path: "non_existent", Error: true},
	}
	for name, tc := range tt {
		tc := tc
		t.Run(name, func(t *testing.T) {
			actual, err := Read(tc.Path)
			if !tc.Error { // Assert not used for readability
				assert.Nil(t, err)
			}
			assert.Equal(t, tc.Expected, actual)
		})
	}
}

[top] [back]

Figure 3

dog/dog.go

package dog

...

type Dog struct {
}

func New() *Dog {
	return &Dog{}
}

func (d *Dog) Bark() string {
	return "bark!"
}

type IDog interface {
	Bark() string
}

var _ IDog = (*Dog)(nil)

main.go

package main

...

func main() {
	d := dog.New()
	PrintMsg(d)
}
func GetSound(t dog.IDog) string {
	return t.Bark()
}

main_test.go

package main

...

type MockDog struct {
	dog.IDog
	bark string
}

func (m *MockDog) Bark() string {
	return m.bark
}

func TestGetSound(t *testing.T) {
	expected := "yip!"
	md := &MockDog{bark: expected}
	actual := GetSound(md)
    assert.Equal(t, expected, actual)
}

[top] [back]

Figure 4

dog/mock_dog/mock_dog.go

// Code generated by MockGen. DO NOT EDIT.
// Source: ..\dog.go

// Package mock_dog is a generated GoMock package.
package mock_dog

import (
	gomock "github.com/golang/mock/gomock"
	reflect "reflect"
)

// MockIDog is a mock of IDog interface
type MockIDog struct {
	ctrl     *gomock.Controller
	recorder *MockIDogMockRecorder
}

// MockIDogMockRecorder is the mock recorder for MockIDog
type MockIDogMockRecorder struct {
	mock *MockIDog
}

// NewMockIDog creates a new mock instance
func NewMockIDog(ctrl *gomock.Controller) *MockIDog {
	mock := &MockIDog{ctrl: ctrl}
	mock.recorder = &MockIDogMockRecorder{mock}
	return mock
}

// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockIDog) EXPECT() *MockIDogMockRecorder {
	return m.recorder
}

// Bark mocks base method
func (m *MockIDog) Bark() string {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "Bark")
	ret0, _ := ret[0].(string)
	return ret0
}

// Bark indicates an expected call of Bark
func (mr *MockIDogMockRecorder) Bark() *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Bark", reflect.TypeOf((*MockIDog)(nil).Bark))
}

main_test.go

package main

...

func TestSound(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	expected := "yip!"
	m := mock_dog.NewMockIDog(ctrl)
	m.EXPECT().Bark().Times(1).Return(expected)

	actual := sound(m)
	assert.Equal(t, expected, actual)
}

[top] [back]

Terraform

The GRACE development team will use the Terraform Best Practices GitBook by Anton Babenko as a style guide for GRACE Terraform code. The team will document additional clarification, modifications, deviations and addendums to the guide in this repository.

[top]

References for Terraform Style Guides

  1. Terraform Best Practices
  2. Hashicorp Terraform Style Conventions
  3. Hashicorp Terraform Recommended Practices
  4. Hashicorp Terraform Getting Started - AWS Dependencies
  5. GRACE IAM Naming Standard-Draft
  6. Jon Brouse Terraform Style Guide
  7. ASICS Digital Group Terraform Style Guide
  8. How to use Terraform as a team – Gruntwork - Gruntwork Blog
  9. Introducing Terraform at GumGum
  10. Template/Module iteration terraform style - Google Groups
  11. Pete's Terraform Tips – Pete Shima – Medium

[top]

AWS Resource Naming

IAM

There is a draft GRACE IAM Naming Standard in Google Docs.

Public domain

This project is in the worldwide public domain. As stated in CONTRIBUTING:

This project is in the public domain within the United States, and copyright and related rights in the work worldwide are waived through the CC0 1.0 Universal public domain dedication.

All contributions to this project will be released under the CC0 dedication. By submitting a pull request, you are agreeing to comply with this waiver of copyright interest.

[top]