Skip to main content

Testing and Benchmarking in Go

Go has a built-in testing framework provided by the testing package and the go test command. This makes writing tests a natural part of the development workflow, not an external dependency.

Writing Tests

Test files reside in the same package as the code they test, but with a _test.go suffix. For example, math.go would be tested by math_test.go.

Test Functions

A test function must start with Test and take a single argument t *testing.T.
package math

import "testing"

func TestAdd(t *testing.T) {
    got := Add(1, 2)
    want := 3
    if got != want {
        t.Errorf("Add(1, 2) = %d; want %d", got, want)
    }
}

Table-Driven Tests

Go developers prefer “table-driven tests” to easily cover multiple test cases without repeating code.
func TestAddTable(t *testing.T) {
    tests := []struct {
        name string
        a, b int
        want int
    }{
        {"positive", 1, 2, 3},
        {"negative", -1, -1, -2},
        {"mixed", -1, 1, 0},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got := Add(tt.a, tt.b)
            if got != tt.want {
                t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, got, tt.want)
            }
        })
    }
}

Subtests (t.Run)

The t.Run method allows you to define subtests. This is useful for hierarchical test reporting and running specific subtests via the -run flag.

Benchmarking

Go also has built-in support for benchmarking code to measure performance. Benchmark functions start with Benchmark and take b *testing.B.
func BenchmarkAdd(b *testing.B) {
    // b.N is automatically adjusted by the runtime to get a statistically significant result
    for i := 0; i < b.N; i++ {
        Add(1, 2)
    }
}
Run benchmarks with go test -bench=..

Examples

Example functions serve two purposes: they act as documentation (appearing in Godoc) and as verified tests. They start with Example and use // Output: comments to verify results.
func ExampleAdd() {
    sum := Add(1, 5)
    fmt.Println(sum)
    // Output: 6
}

Fuzzing

Go 1.18 introduced native support for fuzzing, which generates random inputs to find edge cases and bugs.
func FuzzAdd(f *testing.F) {
    f.Add(1, 2) // Seed corpus
    f.Fuzz(func(t *testing.T, a, b int) {
        Add(a, b) // Check for panics or property violations
    })
}

Test Coverage

You can check code coverage with the -cover flag.
go test -cover
For a detailed HTML report:
go test -coverprofile=coverage.out
go tool cover -html=coverage.out

Summary

  • go test: The standard command to run tests.
  • TestXxx: Unit tests.
  • Table-Driven Tests: Idiomatic way to structure tests.
  • BenchmarkXxx: Performance tests.
  • ExampleXxx: Documentation that is also tested.
  • Fuzzing: Automated random testing to find edge cases.