--- title: "Learning Go: Day Five" date: 2024-05-05T08:00:00.0Z tags: - learning - go excerpt: "For Day Five, I'm going to look at how to write tests" --- Testing is important! I'm an advocate for Test-Driven Development in my work[^1], so it's quite important that I work out how to test what I'm writing before I go any further. For today, I'm using [this really helpful blog post](https://blog.jetbrains.com/go/2022/11/22/comprehensive-guide-to-testing-in-go/) from Jetbrains as a guide. ## Creating a test First of all, I need a separate file for my tests. The convention is apparently to store tests alongside the code they're testing, so I'll create `maths_test.go`: ```go // maths_test.go package maths import "testing" func TestMultiply(t *testing.T) { result := Multiply(2, 5) if result != 10 { t.Errorf("Got %d when expecting %d", result, 10) } } ``` This wrapping function seems to be used similarly to a `describe` block if you're writing tests using `jest` in Javascript. That is, it's a descriptor for a collection of tests, not necessarily a single test. Tests are ran using the `go test` command, however when I ran mine I got this output: ```bash go test ? lewisdale.dev/learn-go [no test files] ``` It turns out I had to specify the package because I'm running it from the module root: ```bash go test lewisdale.dev/learn-go/maths ok lewisdale.dev/learn-go/maths 0.138s ``` And then searching tells me I can also use `./...` as the package name to run all tests recursively: ```bash go test ./... ok lewisdale.dev/learn-go/maths 0.138s ``` ## Triangulating tests These are call table-driven tests, and they amount to iterating over an array (or slice!) of inputs and running a test for each input: ```go func TestMultiply(t *testing.T) { var inputs = []struct { a, b, expected int }{ {2, 5, 10}, {10, 100, 1000}, {12, 15, 180}, } for _, input := range inputs { t.Run(fmt.Sprintf("%d x %d = %d", input.a, input.b, input.expected), func (t *testing.T) { if result := Multiply(input.a, input.b); result != input.expected { t.Errorf("Got %d when expecting %d", result, input.expected) } }) } } ``` The command line doesn't give much information by default, but by adding the verbose (`-v`) flag to the test command we get much better output: ```bash go test ./... -v ? lewisdale.dev/learn-go [no test files] === RUN TestMultiply === RUN TestMultiply/2_x_5_=_10 === RUN TestMultiply/10_x_100_=_1000 === RUN TestMultiply/12_x_15_=_180 --- PASS: TestMultiply (0.00s) --- PASS: TestMultiply/2_x_5_=_10 (0.00s) --- PASS: TestMultiply/10_x_100_=_1000 (0.00s) --- PASS: TestMultiply/12_x_15_=_180 (0.00s) PASS ok lewisdale.dev/learn-go/maths 0.109s ``` ## Fuzzing tests This is a pretty cool feature. Go has a built-in test fuzzer that produces random values and can be used to find edge cases: ```go func FuzzMultiply(f *testing.F) { f.Add(2, 5) f.Fuzz(func (t *testing.T, a, b int) { Multiply(a, b) }) } ``` And then run using `cd maths && go test -fuzz .`: ```bash go test -fuzz . fuzz: elapsed: 0s, gathering baseline coverage: 0/1 completed fuzz: elapsed: 0s, gathering baseline coverage: 1/1 completed, now fuzzing with 10 workers fuzz: elapsed: 3s, execs: 1358234 (452687/sec), new interesting: 0 (total: 1) fuzz: elapsed: 6s, execs: 2742335 (461339/sec), new interesting: 0 (total: 1) fuzz: elapsed: 9s, execs: 4126851 (461439/sec), new interesting: 0 (total: 1) fuzz: elapsed: 12s, execs: 5525464 (466204/sec), new interesting: 0 (total: 1) ^Cfuzz: elapsed: 12s, execs: 5737472 (471088/sec), new interesting: 0 (total: 1) PASS ok lewisdale.dev/learn-go/maths 12.594s ``` This ran my function almost 6 million times in 12 seconds, which is wild. This is a _really_ useful tool for picking up those hard-to-find bugs, but because it will run until there's an error it almost certainly won't be something to use in CI. That's all I'm going to cover on testing today, there's quite a lot more there that I'll start to pick through as I start working on an actual project[^2]. One of the more useful things in the Jetbrains post includes the [Testify package](https://github.com/stretchr/testify), which looks like a testing framework that wraps `testing` for less-verbose tests. [^1]: At least, when I remember to do it, otherwise it's DDT. [^2]: Which I will decide on in the next couple of days