Go Unittest

For how to use testify packages for complex testing, please see <<Go Unittest by testify>>.

Golang has provided built-in testing framework composed of go test command and testing package, here is simple example:

https://go.dev/doc/code#Testing

Table Driven

In go test we usually use table-driven style, where test inputs and expected outputs are listed in a struct and a loop to walk through them, for example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func TestXxx(t *testing.T) {
for _, test := range []struct{
name string
input int
expectedResult int
wantErr bool
err error
wantErrKeyword string
}{
{
name: "normal test",
input: 10,
expectedResult: 20,
wantErr: false,
},
}{
t.Run(test.name, func(t *testing.T){
// test code
})
}
}

Folder for Test Data

For large test data that needs to be read from outside, the go tool will ignore a directory named testdata, making it available to hold ancillary data needed by the tests.

TestMain

If the test code contains a function:

1
2
3
4
5
6
7
func TestMain(m *testing.M) {

// call at end
m.Run()
// previously requires the os.Exist
// https://github.com/golang/go/issues/34129#issuecomment-537598931
}

that function will be called instead of running the tests directly, this may come in handy when you need to do some global set-up/tear-down for your tests, see this example.

Run Test

To run test for specific packge:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# ./... means all packages in this directory (and subdirectories)
go list ./...

# For example the output would be:
example.com/fruit
example.com/world
example.com/zoo

# If you want to run specific tests, such as: "TestHelloWorld", "TestExitWorld"
# in example.com/world package:

# -race: for multi-thread program testing, may not need it
go test -buildvcs=false \
-mod=readonly \
-v -race\
-run "HelloWorld|ExitWorld" \
example.com/world

# To test all packages in the current or sub directories
go test -buildvcs=false \
-mod=readonly \
-v -race \
./...

If you have build tags such as “integration”, “mock” in your test or dependency files head for example:

1
2
// +build integration
// +build mock

To include these taged test files in testing, please use -tags flag:

1
2
3
4
5
6
go test -buildvcs=false \
-mod=readonly \
-v -race \
-tags "integration mock" \ # use space to separate tags
-run "HelloWorld|ExitWorld" \
example.com/world

If you don’t specify the correct -tags then you will get error like:

1
2
3
imports example.com/common.git/cassandra/setup:
build constraints exclude all Go files in
/usr/local/xxx/example.com/gcloud/cs-common-gc.git/cassandra/setup

The -buildvcs, -mod, -race, -tags are build tags from go help build. There is logic to combine and use multiple tags, please see Build Tag Boolean Logic.

NOTE that the go test will always run the test files that don’t have any build tag irrespective of -tags specified or not.

Other test tags please see go help testflag.

Test Coverage

For simplified coverage display only, just use -cover flag:

1
go test -cover ./...

To generate coverage profile for detailed bad coverage analysis:

1
2
3
4
5
6
7
8
9
10
11
12
# Generate simple coverage.out profile
# -covermode=set by default
go test -coverprofile=coverage.out ./...
# Generate coverage with count for each statement.
# flag -race will use covermode=atomic
go test -covermode=count -coverprofile=coverage.out ./...

# Without -o, the output goes to stdout
# Convert to html and you can open via browser
go tool cover -html=coverage.out -o coverage.html
# Coverage for each function
go tool cover -func=coverage.out -o coverage.func

If you use -covermode=count and then go tool the html will show you the intensity of the statements calling count, hover the mouse over the line to see the actual counts.

More details about cover please see this go blog <<The cover story>>.

0%