---
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