#development #golang #testing

Recently, I've invested a lot of time in writing tests for my Go code. Besides fixing an awful lot of bugs by writing the tests, I also discovered I'm writing most of my tests in the same way.

Imagine you have a package with one single function in there:

 1package date
 3import (
 4    "time"
 7// UnixRoundToHour returns the timestamp truncated to the hour.
 8func UnixRoundToHour(unixTime int64) int64 {
 9    t := time.Unix(unixTime, 0).UTC()
10    return t.Truncate(time.Hour).UTC().Unix()

When I want to write the tests for this simple function, it'll probably look as follows:

 1package date_test
 3import (
 4    "testing"
 6    "github.com/stretchr/testify/assert"
 8    "github.com/pieterclaerhout/go-date"
11func Test_UnixRoundToHour(t *testing.T) {
13    type test struct {
14        name     string
15        input    int64
16        expected int64
17    }
19    var tests = []test{
20        {"0", 0, 0},
21        {"1553862120", 1553862120, 1553860800},
22        {"1553862150", 1553862150, 1553860800},
23        {"1553862179", 1553862179, 1553860800},
24        {"1553862181", 1553862181, 1553860800},
25        {"1553860799", 1553860799, 1553857200},
26    }
28    for _, tc := range tests {
29        t.Run(tc.name, func(t *testing.T) {
30            actual := date.UnixRoundToHour(tc.input)
31            assert.Equal(t, tc.expected, actual)
32        })
33    }

The first thing you'll see is that I'm using the standard Go testing package. It offers a good intergration with Visual Studio Code and it's the standard testing tool provided by Go.

The next library I'm using is the assert library from Stretchr. You can go without this library, but I found it makes the tests much more concise and more readable.

Last but not least, I'm importing the package which I'm testing. I'm importing it because the tests live in a separate package (the original package along with the suffix _test). This allows me to test the external interface of the package. I'll write another post in the future about internal tests.

Then, for each function I want to test, I'm writing a test function with Test_<function-name> as the name. I'm using the approach called table driven test to write the test function.

The first thing which gets defined in the test function is the test struct which defines the parameters for each test case. I always try to give each one a name describing the test case as it makes debugging later on easier. In this case, I define a name, the input value and the expected output value.

The struct is defined inline in the function so that the namespace of the tests is kept nice and clean. This, I don't have to come up with unique names for each test function.

Then, the tests are defined. In there, you define each test case along with it's paramters.

Then, we run each test case as a subtest. This again makes test reporting a lot more clear as each subtest gets a proper name (hence the name parameter we defnied in the struct).

For each test case, we test the function and use the assert library to check the result. What is nice about the assert library is that it gives you a very clear message if the result isn't what was expected.

If one of the test cases would result in an error, you'll get a description like this:

 1--- FAIL: Test_UnixRoundToHour (0.00s)
 2    --- FAIL: Test_UnixRoundToHour/1553862120 (0.00s)
 3        /Users/pclaerhout/go-date/date_test.go:31:
 4                Error Trace:    date_test.go:31
 5                Error:          Not equal:
 6                                expected: 1553860900
 7                                actual  : 1553860800
 8                Test:           Test_UnixRoundToHour/1553862120
10exit status 1

In a future post, I'll explain the snippets I've created for Visual Studio Code to write tests in an even faster way…