Test and benchmark your code in go
Marco Franssen /
10 min read • 1950 words
When I started writing my first programs in Go, I noticed the tooling ships out of the box with test and benchmark features. You simply follow some naming conventions when it comes to file names. You import a reference to the "testing" package which is kind of part of the language. Aaaand… Ready set, and of you Go with writing some tests and benchmarks in Go. In my earlier blog post I briefly touched writing a test in Go already. I recommend reading this blogpost whenever you are a real newby with Go.
The conventions
In Go you put your tests in the same folder as the code. The file containing the tests will be postfixed with _test.go
. Don't worry, it won't be shipped in you compiled binary. So lets start with an example folder structure for a Go project to get an idea how that would look like.
At first I had to get used to this kind of structure as I have always been used to put tests in different packages in Java or different assemblies in c# or a test folder in Javascript
projects. In the beginning I was kind of resisting to this, but once I started to write more and more code I figured this helps you when doing TDD. In general tests and code are more close to each other and therefore enforced me to practition TDD even more strictly then I already did with even less effort of navigating folders and files and stuff. Above folder structure shows one main package with code in main.go
and storage.go
. We also have one api package which contains routes and handlers for those routes.
For developers working recently on ReactJS web development you might also have noticed some have started applying same folder structures to have the tests in the same folder as the component.
Writing tests
In Go all tests go inside a file named similar to the file you are testing. So if you want to test code in main.go
, then you write the tests in main_test.go
. In the test file we will always need to import the testing package. Then there is the following signature for every test you write, func TestXxx(*testing.T)
, where the first X MUST be Uppercase.
Benchmarking
Go also has builtin Benchmarking support. This Benchmarking feature is also part of the testing package. A Benchmark has a similar signarture as tests, func BenchmarkXxx(*testing.B)
, where the first X MUST also be uppercase.
Example
In this example we are going to define a struct which we would like to use in our API as a DTO to respond with some JSON. Let's approach this in a TDD manner. I usually start of by creating the files which will contain my code.
- api/models.go
- api/models_test.go
First I will layout some tests to describe what I would like to achieve.
With the test in place I can now define the struct, Contructor function and the instance method on my struct.
Now we are ready to run our test to see if the code works and gives expected output.
As you can see one of our tests is failing. We expected nicely formatted JSON but we got the most compact form of JSON without any formatting. Now we have 2 options. Change our requirement (The Test) or we change the implementation to have the JSON outputted with some formatting. At the end of this blogpost I also provide you a solution to fix the failing test. Before that I would like to zoom in a bit on how to benchmark your code.
Now lets also add a Benchmark to test the performance of our implementation.
Now let us run our Benchmark. In case you still have the failing test case you will notice the benchmark doesn't run. No need to benchmark broken code right. So to bypass that in case you didn't take previous challenge lets skip this failing test using t.Skip()
.
The -8
in this benchmark means that the benchmark ran with GOMAXPROCS=8
, in our case it doesn't really make a difference probably if we would run with only one CPU proc as we don't use any parallelism in our code.
In case you didn't take previous challenge I want to challenge you again. Lets add support to have formatted and unformatted JSON and see how the performance is for both implementations by adding another Benchmark for formatted JSON.
Once ready continue reading, I'll provide you a solution as well for the challenge.
In following example I implemented the full example code in main.go so you can easily test it from the playground.
I also updated the Benchmark and test.
Lets run the updated benchmarks so we can compare the performance of both implementations.
As you notice the performance of the nicely formatted json including newlines and 2 spaces of indentation is a factor 4 slower then the unformatted json.
Bonus
Below I will show you a few more options you can use for running tests and benchmarks.
Test
Following will run test with code coverage.
-covermode=atomic
is the safest option in multithreaded environments. ./...
will run the tests for all packages in the solution.
Following will run the tests using GOMAXPROCS=1
and GOMAXPROCS=4
.
Asserts
As you noticed there is no assertion library included in the standard testing package. In case you want to have assert functions like you are used to in other programming languages, you need to install a third party package. E.g.: testify.
This allows you to write the assertions in following style.
Testify comes also with other features like mocking etc.
Benchmark
Following will run the benchmarks for 15 seconds. The default is 1s. We will run the benchmark twice for every cpu configuration we specify. And we will only run these benchmarks for the tests that match the regex. Using .
as regex would run all benchmarks.
With these options on more complicated scenarios you can make sure there is a more reliable report as you run the benchmark for a longer amount of time and twice. You also notice it doesn't really make a difference if you run it using one or multiple cpus as already predicted earlier. You also notice now the benchmark for 4 cpus is postfixed with -4
. Also note that the pretty formatting now only shows up as a factor 3 slower.
For more options you could check go test --help
. There are for example options to create memory profiles and cpu profiles which allow you to analyze the performance. Looking forward to your feedback, as there is never a limit to what a person can learn.
The full solution can be downloaded here.