4. Testing
Writing Tests
Tests are defined as functions in the following form:
func TestXxx(t *testing.T)
The tests reside in the same directory as the source code. The test files end with _test.go. The code in these files is not compiled into the binary when building the project. For demonstration purposes we put the function and test in the same code block. These are usually in a different file (e.g. calculator.go and calculator_test.go).
| |
Output:
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
PASS
Test failures
There are two methods to output test failures:
t.Errorprints the error without stopping the current testt.Fatalaborts the current test
Both variants also support template strings by appending f:
if got != expected {
t.Fatalf("Wrong output returned. Got: %v Expected: %v", got, expected)
}
Try and be explicit with your error messages. It should be clear:
- What is being tested
- What was received
- What was expected
Running tests
# Run all tests in the currenty directory
go test
# Run all tests in the current directory and all subdirectories
go test ./...
# Run a single test
go test -run TestAdd
# Clean the cache, so that all tests are rerun
go clean -testcache
Compare structs and slices
It should be noted that comparing structs with == only works for simple cases. As soon as the struct contains pointers or slices a different method has to be used. There are two variants:
- reflect.DeepEqual
is older and in the standard library. However it has less features (e.g. no
Difffunction) and cannot compare things liketimein different time zones. - go-cmp is a new library that makes comparing and printing the output easy. It should only be used for tests and never in production code. We recommend using this library.
| |
Output:
=== RUN TestEqual
Compare simple struct: true
Compare struct with pointer false
Compare complex structs with reflect true
Compare complex structs with go-cmp true
Diff of two structs with go-cmp main.User{
Name: "Andrea",
- Role: &main.Role{Name: "admin"},
+ Role: &main.Role{Name: "user"},
}
--- PASS: TestEqual (0.00s)
PASS
Table driven tests
Instead of writing many small tests, we prefer to group tests that test a single function. This is achieved by putting all the test cases in a single “table”. The table contains input and expected output. We then loop over the table and check if the function returns the expected output.
| |
Output:
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
PASS
Subtests
A single test can be run with go test -run TestAdd. However with table driven tests, we cannot run a single test case from the table. The testing package allows us to Run another test within our test. The format is:
t.Run("test name", func(t *testing.T) {
// The test comes here
})
| |
Output:
=== RUN TestAdd
=== RUN TestAdd/Add(2,3)
=== RUN TestAdd/Add(2,-3)
--- PASS: TestAdd (0.00s)
--- PASS: TestAdd/Add(2,3) (0.00s)
--- PASS: TestAdd/Add(2,-3) (0.00s)
PASS
Now a single test can be run:
go test -v -run 'TestAdd/Add\(2,3\)'
We need to escape the parentheses, because the value is interpreted as a regex. The -v flag makes the command output more verbose.
Coverage
The test coverage tells us how much of our code is tested.
go test -cover
To see exactly which lines are not tested, run the following:
# Generates coverage report
go test -coverprofile=coverage.out
# Opens browser with detailed report
go tool cover -html=coverage.out
Benchmarks
To test the performance of function we can write benchmarks. These are also placed in the _test.go files and following format:
func BenchmarkXxx(b *testing.B) {
for n := 0; n < b.N; n++ {
// Call a function here
}
}
Run the benchmarks with go test -bench .