follow me

Go: testing

As developers, we all know how important testing our code is. If you haven't tested what you've written, you can't really say you've created it. In the early stages of developing an app, we tend to manually test our code. But as more components and features are added, the code base will become large and unwieldy. Manually performing regression tests to ensure what you wrote one year ago still works after your latest merge yesterday. Manual testing is simply not scalable.

Go has a built-in testing command called go test and a package testing which combine to give a minimal but complete testing experience. The standard tool chain also includes benchmarking and statement based code coverage

Unit testing in Go is just as opinionated as any other aspect of the language like formatting or naming. The syntax deliberately avoids the use of assertions and leaves the responsibility for checking values and behaviour to the developer.

Here is an example of a method we want to test in the main package. We have defined an exported function called Sum which takes in two integers and adds them together.

package main

func Sum(x int, y int) int {
    return x + y

func main() {
    Sum(5, 5)

To write a new test suite, create a file whose name ends _test.go that contains the TestXxx functions as described here. Put the file in the same package as the one being tested. The file will be excluded from regular package builds but will be included when the go test command is run.

package main

import (

func TestSum(t *testing.T) {
    total := Sum(5, 5)
    if total != 10 {
       t.Errorf("Error: result=%d expect=%d.", total, 10)

There are two ways to launch tests for a package. These methods work for unit tests and integration tests alike. Within the same directory as the test:

go test

This picks up any files matching somename_test.go or by fully-qualified package name:

go test github.com/login/package

You have now run a unit test in Go, for a more verbose output type in go test -v and you will see the PASS/FAIL result of each test including any extra logging produced by t.Log. The go test tool has built-in code-coverage for statements. To try it with out example above type in:

$ go test -cover
coverage: 50.0% of statements

ok  	github.com/login/package	0.009s

High statement coverage is better than lower or no coverage, but metrics can be misleading. We want to make sure that we're not only executing statements, but that we're verifying behaviour and output values and raising errors for discrepancies. If you delete the "if" statement from our previous test it will retain 50% test coverage but lose its usefulness in verifying the behaviour of the "Sum" method.

When you have some slowly running tests, it gets annoying to wait for all the tests to complete, especially when you want to know right away whether the build runs. The solution to this problem is to move slowly running tests to *_slow_test.go files and add build tag in the beginning of the file. For example:

// +build slow

This way go test won't include tests, which have build tags. In order to run all the tests you have to specify build tag in go test:

go test -tags=slow