Testing in golang is much like any other language, and Go even comes with some of its own tools to write your tests with.
Today I’m going to share a couple of the tricks I’ve learned to make sure the code comes out smelling like a patch of roses with a sensible amount of code coverage
Build Tools
I always have some makefile targets to run my tests and build tags in my code to run all my tests.
Makefile target
If you’re like me and have trouble remembering all the intricacies of a command, it’s definitely worth having a target which will test everything for you
coverage: vendor
mkdir -p reports || exit 0
go test -v -tags unit -coverprofile=reports/cover.out ./...
This will run the tests tagged with unit and put the coverage
report out to reports/cover.out
. I tend to also have
a variable to hold my mkdir
command, since Windows does
magical things if you run the wrong thing.
Build tags
One of the coolest things in the Go toolchain is build tags, where you can compile in different functionality based on a variable passed in at build time. To add these, simply add a line like this to the top of your files
// +build unit
This is then passed as a parameter to our test command
go test -v -tags unit ./...
Properly tagging your tests means that you split up your integration tests and run them when appropriate rather than having to run them in tandem with the unit tests, allowing you to fail as quickly as possible.
Coding conventions
I’ve seen a few schools of thought on this, but I definitely lay out my code in a particular way to encourage component reuse across my repository.
Interfaces
I tend to use interfaces, but an alternative I’ve seen going around is assigning functions to fields at runtime. I don’t think either is necessarily wrong, but I feel more comfortable using interfaces having come from the Java world.
The drawback to my preferred way of doing it is that it introduces the need to mock out interfaces where I use them. I generate these with Mockery, though I don’t think it’s in active development any more and I should probably find an alternative.
Pass state in function parameters
In circumstances where I don’t have structs, I will pass state in and avoid global variables completely. This means that I have predictable input to the function, and I can know what to expect when it returns.
Golang provides a nice construct called context
to support
this.
Smaller, more predictable functions
I’m of the opinion that code should be readable before it’s performant, and composable functions should be at the core of that.
Smaller functions are easier to test, so if it makes it easier to read the code I’ll generally refactor a little to pull it out.
No magic in web services
Something that always frustrated me about Spring when I was in the Java world was all the magic injections and wrappers around requests, to the point where it’s difficult to untangle and isolate functionality for testing.
Golang’s raw HTTP package is nice enough to give you the
request and response objects and that’s it. Everything has to
be wired up mostly manually, and this means you can test HTTP
services quite easily. Use the httptest
package to get some
useful constructs like the httptest.Server
httptest.ResponseRecorder
.
There are some good examples on Golang.org
about how to use the httptest
package, which I’ll reproduce
here
package main
// httptest.Server example
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
)
func main() {
ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s", r.Proto)
}))
ts.EnableHTTP2 = true
ts.StartTLS()
defer ts.Close()
res, err := ts.Client().Get(ts.URL)
if err != nil {
log.Fatal(err)
}
greeting, err := ioutil.ReadAll(res.Body)
res.Body.Close()
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s", greeting)
}
package main
// httptest.ResponseRecorder example
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
)
func main() {
handler := func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "<html><body>Hello World!</body></html>")
}
req := httptest.NewRequest("GET", "http://example.com/foo", nil)
w := httptest.NewRecorder()
handler(w, req)
resp := w.Result()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(resp.StatusCode)
fmt.Println(resp.Header.Get("Content-Type"))
fmt.Println(string(body))
}
And of course, my most important tip…
Test Driven Development
Writing tests first is a sure fire way to make sure you get them
done and design your code in such a way to support it. I usually
achieve this by creating a function that calls panic
,
then writing my test cases, and finally implementing the
function.
It’s something I’ve been trying to win my team around to, but I think it’s going to take a while to convince them, and it does feel completely ass backwards when you first try it. I can guarantee your testing coverage will go up and you’ll find yourself giving more thought to the overall structure of your code.