8.1 Writing Tests

Go's testing is powerful because it's built-in (no external testing framework needed), fast (tests run in parallel by default), simple (minimal boilerplate), and integrated (works seamlessly with go test command).

Basic test structure

Tests are functions that start with Test and take *testing.T:

package main

import "testing"

func Add(a, b int) int {
    return a + b
}

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

Run tests using the go test command:

go test                   # Run all tests in current package
go test -v                # Verbose output
go test ./...             # Run tests in all packages
go test -run TestAdd      # Run specific test

Test file naming

Go's tooling automatically discovers tests based on naming: *_test.go files are excluded from normal builds, Test* functions are automatically identified as tests, and no configuration is needed—just follow the convention.

Test functions

package main

import "testing"

func TestBasic(t *testing.T) {
    // Basic test
    if 1+1 != 2 {
        t.Error("Math is broken!")
    }
}

func TestWithHelper(t *testing.T) {
    // Helper functions for common assertions
    assertEqual(t, 2+2, 4)
}

func assertEqual(t *testing.T, got, want int) {
    t.Helper()  // Marks this as a helper function
    if got != want {
        t.Errorf("got %d, want %d", got, want)
    }
}

t.Helper() marks a function as a test helper. When tests fail, the error is reported at the call site, not in the helper function, making debugging easier.

Test methods

func TestMethod(t *testing.T) {
    t.Log("This is a log message")        // Log message
    t.Error("This is an error")           // Log error and continue
    t.Fatal("This stops the test")        // Log error and stop test
    t.Skip("Skip this test")              // Skip test
    t.Parallel()                          // Run in parallel
}