SyntaxStudy
Sign Up
Go Writing Tests with the testing Package
Go Beginner 1 min read

Writing Tests with the testing Package

Go has a built-in testing framework in the `testing` package. Test files must end with `_test.go` and test functions must be named `TestXxx` where Xxx starts with an uppercase letter. Test functions receive a `*testing.T` parameter used to report failures with methods such as `t.Error`, `t.Errorf`, `t.Fatal`, and `t.Fatalf`. Tests are run with `go test ./...`. Table-driven tests are the dominant Go testing pattern. Instead of writing many individual test functions, you define a slice of test cases (a table) with inputs and expected outputs, then iterate over the table running each case as a sub-test using `t.Run`. Sub-tests can be run individually, run in parallel, and produce clear failure messages that include the test case name. The `t.Helper()` call marks a function as a test helper so that failure messages report the line in the calling test, not inside the helper. The `t.Parallel()` call marks a test as safe to run in parallel with other parallel tests, which can significantly reduce the total test time for I/O-bound test suites.
Example
// math/math.go
package math

import "errors"

func Divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

// math/math_test.go
package math_test

import (
    "math"         // standard library math for comparison
    "testing"

    mymath "github.com/example/app/math"
)

func TestDivide(t *testing.T) {
    t.Parallel()

    tests := []struct {
        name    string
        a, b    float64
        want    float64
        wantErr bool
    }{
        {"positive", 10, 2, 5, false},
        {"negative divisor", -6, 3, -2, false},
        {"float", 1, 3, 1.0 / 3.0, false},
        {"divide by zero", 5, 0, 0, true},
    }

    for _, tc := range tests {
        tc := tc // capture range variable
        t.Run(tc.name, func(t *testing.T) {
            t.Parallel()
            got, err := mymath.Divide(tc.a, tc.b)
            if (err != nil) != tc.wantErr {
                t.Errorf("Divide(%v, %v) error = %v, wantErr %v",
                    tc.a, tc.b, err, tc.wantErr)
                return
            }
            if !tc.wantErr && math.Abs(got-tc.want) > 1e-9 {
                t.Errorf("Divide(%v, %v) = %v, want %v", tc.a, tc.b, got, tc.want)
            }
        })
    }
}