go specification programming essential tools (code specification and single test)

Preface

Good use of tools in go development can greatly facilitate standardized development, the first thing you need to do is to standardize and test.

  1. Generally speaking, usage habits are a key gofmt goimports must be used. Go vet golangci-link assists in checking semantics and correcting them.
  2. Complete the necessary basic use cases, form-based single go test, and use gomonkey if external

After the above two points are completed, we can say that the basic specification requirements have been completed.

Basics

# standard doc look up 
go doc cmd/gofmt

# go version update
go get -u
brew upgrade go

Format and Code Specification

// Basic format updates
// Gofmt, most of the formatting problems can be solved by gofmt, gofmt automatically formats the code to ensure that all go codes are consistent with the officially recommended format, so all formatting problems are based on the results of gofmt.
go fmt .
go help fmt

// Including fmt will not optimize the reference package format, it must be applied
// goimports, this tool adds automatic deletion and introduction of packages based on gofmt.
goimports -w .

// go vet, vet tool can help us to analyze all kinds of problems in our source code statically, such as redundant code, the logic of early return, whether struct tag meets the standard, etc. Perform code static analysis before compiling.
go vet .

// golint, a tool like jslint in javascript, is designed to detect irregularities in code.
// https://github.com/golangci/golangci-lint
golangci-lint run -c ~/.golangci.yaml . 

golangci-lint version
golangci-lint --help
golangci-lint run .
golangci-lint run -c ~/.golangci.yaml $ProjectFileDir$/...

Single Test

base test

  1. Example (actually using an ide right-click goland test for function package)
go test -v

The code for calc.go is as follows:

package main

func Add(a int, b int) int {
    return a + b
}

func Mul(a int, b int) int {
    return a * b
}

Then calc_ Test. The test cases in go can be written as follows:

package main

import "testing"

func TestAdd(t *testing.T) {
	if ans := Add(1, 2); ans != 3 {
		t.Errorf("1 + 2 expected be 3, but %d got", ans)
	}
	assert.Equal(t, add(1, 2), 3, "Add Error!")

	if ans := Add(-10, -20); ans != -30 {
		t.Errorf("-10 + -20 expected be -30, but %d got", ans)
	}
}
  • The test case name is generally named Test plus the method name to be tested.
  • There is one and only one parameter for the test, t *test here. T.
  • The parameter of the benchmark is *test. B, the parameter of TestMain is *testing.M type.

The go test -v, -v parameter displays the test results for each use case, and the -cover parameter can view coverage.

If you only want to run one of these cases, such as TestAdd, you can specify it with the -run parameter, which supports wildcards*, and some regular expressions, such as ^, $.

$ go test -run TestAdd -v
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
PASS
ok      example 0.007s

setup and teardown

If the logic before and after each test case is the same in the same test file, it is typically written in the setup and teardown functions. For example, the object to be tested needs to be instantiated before execution. If the object is complex, it is appropriate to extract this part of the logic. After execution, you may do some work on resource recycling classes, such as closing network connections, releasing files, and so on. The standard library testing provides such a mechanism:

func setup() {
	fmt.Println("Before all tests")
}

func teardown() {
	fmt.Println("After all tests")
}

func Test1(t *testing.T) {
	fmt.Println("I'm test1")
}

func Test2(t *testing.T) {
	fmt.Println("I'm test2")
}

func TestMain(m *testing.M) {
	setup()
	code := m.Run()
	teardown()
	os.Exit(code)
}
  • In this test file, there are two test cases, Test1 and Test2.
  • If the test file contains the function TestMain, the generated test will call TestMain(m) instead of running the test directly.
  • Calling m.Run() triggers the execution of all test cases and uses os.Exit() processes the returned status code, if it is not zero, indicating a case failure.
  • So you can do some extra setup and teardown work before and after calling m.Run().

Executing go test will output

$ go test
Before all tests
I'm test1
I'm test2
PASS
After all tests
ok      example 0.006s

come from https://geektutu.com/post/quick-go-test.html

go monkey

introduce

monkey Is a very common piling tool in Go unit testing. It rewrites the executable file in assembly language at runtime, jumps the implementation of the objective function or method to the piling implementation, and works like hot patch.

The monkey library is powerful, but here are some things to note when using it:

  • Money does not support inline functions, so you need to turn off the Go Language inline optimization through the command line parameter -gcflags=-l when testing.
  • Money is not thread safe, so don't use it in concurrent unit tests.

All the code in the example is here: https://github.com/fishingfly/gomonkey_examples

Execute unit tests:

Note: The -gcflags=-l parameter is added here to prevent inline optimization.

go test -run=TestMyFunc -v -gcflags=-l
go test -gcflags=all=-l

convey can be used

convey.Convey("case Name", t, func() {
  Specific Tests case
  convey.So(...) //Assertion
})

mock method and member method

// Method patch	
patchDeleteKey := gomonkey.ApplyMethod(reflect.TypeOf(&clients.RedisClientImpl{}), "DeleteKey",
		func(c *clients.RedisClientImpl, ctx context.Context, key string) (int, error) {
			return 1, nil
		})
	defer patchDeleteKey.Reset()

	patchConfig := gomonkey.ApplyFunc(config.GetJSON,
		func(key string, val interface{}) error {
			log.Error("lalalalala")
			return nil
		})
	defer patchConfig.Reset()

// Interface method patch
	patchBatchGetAppInfo := gomonkey.ApplyMethod(reflect.TypeOf(&clients.GameClientImpl{}), "BatchGetAppInfo",
		func(c *clients.GameClientImpl, ctx context.Context, appids []string) (map[string]*pb.StAppInfoResult, error) {
			return map[string]*pb.StAppInfoResult{
				"11059723": &pb.StAppInfoResult{
					AppInfo: &pb.StAppInfo{
						Appid:    "11059307",
						Platform: &wrappers.Int32Value{Value: 0},
					},
				},
			}, nil
		})
	defer patchBatchGetAppInfo.Reset()

	gameManager := NewGameManager()
	ctx := context.TODO()
	req := &pb.GetGameHotListReq{
		PlatformId:     "1002",
		DevicePlatform: &wrappers.Int32Value{Value: 1},
	}
	result, err := gameManager.GetGameHotList(ctx, req, 3)
	assert.Equal(t, 6, len(result))
	assert.Nil(t, err)

mock global variable ApplyGlobalVar

// Address of @param[in] target global variable
// Piles for @param[in] double global variable
func ApplyGlobalVar(target, double interface{}) *Patches
func (this *Patches) ApplyGlobalVar(target, double interface{}) *Patches

The global variable mock is simple, just look at the code:

var num = 10

func TestApplyGlobalVar(t *testing.T) {
    Convey("TestApplyGlobalVar", t, func() {

        Convey("change", func() {
            patches := ApplyGlobalVar(&num, 150)
            defer patches.Reset()
            So(num, ShouldEqual, 150)
        })

        Convey("recover", func() {
            So(num, ShouldEqual, 10)
        })
    })
}

mock function variable ApplyFuncVar

// Address of @param[in] target function variable
// Definition of @param[in] double stake function
func ApplyFuncVar(target, double interface{}) *Patches
func (this *Patches) ApplyFuncVar(target, double interface{}) *Patches
var funcVar = func(a,b int) (int,error) {
    if a < 0 && b < 0 {
        errmsg := "a<0 && b<0"
        return 0, fmt.Errorf("%v",errmsg)
    }
    return a+b, nil
}

func TestMockFuncVar(t *testing.T) {
    Convey("TestMockFuncVar", t, func() {
        gomonkey.ApplyFuncVar(&funcVar, func(a,b int)(int,error) {
            return a-b, nil
        })
        
        v, err := funcVar(20, 5)
        So(v, ShouldEqual, 15)
        So(err, ShouldBeNil)

    })
}

Author: 123 archu
Links: https://www.jianshu.com/p/adb8493f44e1

Enclosure

golangci-lint installation

curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.43.0

https://golangci-lint.run/usage/install/

curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.40.0

go get -u github.com/golangci/golangci-lint/cmd/golangci-lint@v1.44.0


# Note version issues
# You can install with brew

https://github.com/golangci/golangci-lint

goland Configuration golinter

  1. Install go linter

  2. Configure go linter

  3. Configuring golanglint-ci in File Watcher

    run -c ~/.golangci.yaml $ProjectFileDir$/...
    

Usage

Open a project, insert an empty line, save it, and in the output bar below, show the project's code checking problems, and automatically fix the scanned code problems

configuration file

https://git.woa.com/standards/go/blob/master/.golangci.yml

run -c ~/.golangci.yaml P r o j e c t F i l e D i r ProjectFileDir ProjectFileDir/...

.golangci.yml

# Full Version https://golangci-lint.run/usage/configuration/
linters-settings:
  funlen:
    lines: 80
    statements: 80
  goconst:
    min-len: 2
    min-occurrences: 2
  gocyclo:
    min-complexity: 20
  goimports:
    #
  revive:
    confidence: 0
  govet:
    check-shadowing: true
  lll:
    line-length: 120
  errcheck:
    check-type-assertions: true
  gocritic:
    enabled-checks:
      - nestingReduce
      - commentFormatting
    settings:
      nestingReduce:
        bodyWidth: 5

linters:
  disable-all: true
  enable:
    - deadcode
    - funlen
    - goconst
    - gocyclo
    - gofmt
    - ineffassign
    - staticcheck
    - structcheck 
    - typecheck
    - goimports
    - revive
    - gosimple
    - govet
    - lll
    - rowserrcheck
    - errcheck
    - unused
    - varcheck
    - sqlclosecheck
    - gocritic

run:
  timeout: 20m

issues:
  exclude-use-default: true


  include:
    - EXC0004 # govet (possible misuse of unsafe.Pointer|should have signature)
    - EXC0005 # staticcheck ineffective break statement. Did you mean to break out of the outer loop
    - EXC0012 # revive exported (method|function|type|const) (.+) should have comment or be unexported
    - EXC0013 # revive package comment should be of the form "(.+)...
    - EXC0014 # revive comment on exported (.+) should be of the form "(.+)..."
    - EXC0015 # revive should have a package comment, unless it's in another file for this package

  exclude-rules:
    - path: _test\.go
      linters:
        - funlen 
    - linters:
        - staticcheck
      text: "SA6002: argument should be pointer-like to avoid allocations" # sync.pool.Put(buf), slice `var buf []byte` will tiger this
    - linters:
        - structcheck
      text: "Store` is unused" 
    - linters:
        - lll
      source: "^//go:generate " # Exclude lll issues for long lines with go:generate

  max-same-issues: 0
  new: false
  max-issues-per-linter: 0

output:
  sort-results: true

service:
  golangci-lint-version: 1.28.x
 

Keywords: Go Back-end

Added by loftystew on Mon, 07 Feb 2022 19:17:53 +0200