Published on

Unit Testing the Go Way

Authors

About Assertion Libraries

When writing unit tests or practising test-driven development (TDD), my experience with other programming languages has been to grab an assertion library to do the heavy lifting for me. It’s just convenient. I know what I want to test, but sometimes how to verify, organise and write the tests can be a bit tedious. With assertion libraries, you have some sort of framework. For example, if I wanted to test that an add function, which takes two arguments, say 2 and 2 should return 4 in Go:

func add(2, 2)

with an assertion library, like testify, I would do something like this:

Assert.Equal(4, func add(2,2))

Testify is an assertion library written in Go and the above is an example of how you would write your tests if using the library.

A JavaScript testing framework like jest also provides methods like describe, test, and it that help you organise your tests based on scenarios. When your tests fail, you can inspect your test code by searching for the statement marked as failed in the logs. It’s common for a lot of programming languages to have an assertion library either as part of the standard library or as an open-source testing framework to help with testing in this manner.

Go is different.

When I first picked up Go, and when it came to writing tests, my first instinct was to reach out for an assertion library to do the heavy lifting for me. I knew the Go library came with a testing package but I didn’t quite understand how to use it properly or how Go testing really worked. So I used the 'testify' assertion library. It just seemed intuitive, having used other assertion libraries in other languages.

Taking a Turn and Thinking About Errors

After taking a few lessons under the tutelage of Bitfield Consulting run by John Arundel, a Go teacher and author, I realised using a library like testify acted as a crutch for me and prevented me from really thinking about unit testing the Go way. Not that there’s anything wrong with an assertion library, but for someone new to learning Go, it was a sort of barrier to seeing the full power of Go and learning to write idiomatic Go tests. Yes, testing can be idiomatic too! After all, it’s code.

Go doesn’t have the concept of exceptions like some languages do. You often return an error. Errors are values and not exceptions. Go normalises errors. The concept of multiple return values in Go makes this possible. If a program method is meant to fail, it returns an error value that contains the message, more so when it is a package. It’s up to the caller to check for an error value and do something about it, or ignore it (please don’t do the latter). You’ll often see error-handling like this in Go programs:

If err != nil {

    // do something`

}

So what have errors got to do with writing tests in Go?

Testing for Failure

If you look through some tests in the Go standard library source code, you’ll see a lot of want and got variables which are the equivalent of expected and actual, respectively in some assertion libraries. And what Go encourages you to do with the values of these test variables is to check that they are NOT equal. So you write something like the following:

If want != got {

    t.Errorf(“Your error message here”)

}

This may seem unintuitive if you're used to testing in other languages.

However, it reduces noise. Think of debugging a failed test in a CI pipeline or even right on your own machine. You really want to know when something has gone wrong rather than having to read through all of your test cases. This makes the overall developer experience much more pleasant.

Another thing I appreciate about this style of testing is that it forces me to think first about how my program might fail. The Go way of testing seems to encourage the idea of putting the possibilities of failure at the forefront of developing or writing a solution, so you can handle them gracefully.

The Go way of testing makes this a natural part of the language. After all, language shapes our thinking.