We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
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:
package date
import (
"time"
)
// UnixRoundToHour returns the timestamp truncated to the hour.
func UnixRoundToHour(unixTime int64) int64 {
t := time.Unix(unixTime, 0).UTC()
return t.Truncate(time.Hour).UTC().Unix()
}
When I want to write the tests for this simple function, it'll probably look as follows:
package date_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/pieterclaerhout/go-date"
)
func Test_UnixRoundToHour(t *testing.T) {
type test struct {
name string
input int64
expected int64
}
var tests = []test{
{"0", 0, 0},
{"1553862120", 1553862120, 1553860800},
{"1553862150", 1553862150, 1553860800},
{"1553862179", 1553862179, 1553860800},
{"1553862181", 1553862181, 1553860800},
{"1553860799", 1553860799, 1553857200},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
actual := date.UnixRoundToHour(tc.input)
assert.Equal(t, tc.expected, actual)
})
}
}
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:
--- FAIL: Test_UnixRoundToHour (0.00s)
--- FAIL: Test_UnixRoundToHour/1553862120 (0.00s)
/Users/pclaerhout/go-date/date_test.go:31:
Error Trace: date_test.go:31
Error: Not equal:
expected: 1553860900
actual : 1553860800
Test: Test_UnixRoundToHour/1553862120
FAIL
exit 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…
If this post was enjoyable or useful for you, please share it! If you have comments, questions, or feedback, you can email my personal email. To get new posts, subscribe use the RSS feed.