Testing Fundamentals

Updated

July 30, 2026

Overview

Go has built-in testing support. Test files end with _test.go and use the testing package.

Writing Tests

// math.go
package math

func Add(a, b int) int {
    return a + b
}
// math_test.go
package math

import "testing"

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

Running Tests

go test              # Current package
go test ./...        # All packages
go test -v           # Verbose
go test -run TestAdd # Specific test

Test Functions

func TestXxx(t *testing.T)          // Test
func BenchmarkXxx(b *testing.B)     // Benchmark
func ExampleXxx()                   // Example
func FuzzXxx(f *testing.F)          // Fuzz test

t.Error vs t.Fatal

func TestExample(t *testing.T) {
    // Continues after failure
    t.Error("failed but continuing")

    // Stops test immediately
    t.Fatal("failed, stopping now")
}

Helper Functions

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

func TestMath(t *testing.T) {
    assertEqual(t, Add(1, 2), 3)
}

Setup and Teardown

func TestMain(m *testing.M) {
    // Setup
    setup()

    code := m.Run()  // Run tests

    // Teardown
    teardown()
    os.Exit(code)
}

Parallel Tests

func TestParallel(t *testing.T) {
    t.Parallel()  // Run in parallel
    // Test code
}

Summary

Command Purpose
go test Run tests
go test -v Verbose output
go test -cover Show coverage
go test -race Race detection

Worked example

t.Helper, Error vs Fatal, and TestMain setup/teardown.

Save as strutil.go and strutil_test.go. Then:

go mod init example
go test -v
// strutil.go
package main

import "strings"

func TitleWords(s string) string {
    parts := strings.Fields(s)
    for i, p := range parts {
        if p == "" {
            continue
        }
        parts[i] = strings.ToUpper(p[:1]) + strings.ToLower(p[1:])
    }
    return strings.Join(parts, " ")
}
// strutil_test.go
package main

import (
    "fmt"
    "os"
    "testing"
)

func TestMain(m *testing.M) {
    fmt.Println("setup: suite start")
    code := m.Run()
    fmt.Println("teardown: suite end")
    os.Exit(code)
}

func assertEq(t *testing.T, got, want string) {
    t.Helper()
    if got != want {
        t.Errorf("got %q want %q", got, want)
    }
}

func TestTitleWords(t *testing.T) {
    assertEq(t, TitleWords("hello world"), "Hello World")
    assertEq(t, TitleWords("gO"), "Go")
}

func TestTitleWordsFatalStops(t *testing.T) {
    if TitleWords("") != "" {
        t.Fatal("empty should stay empty") // would stop this test
    }
    // Continues only if Fatal did not fire.
    assertEq(t, TitleWords("a b"), "A B")
}

Expected output:

setup: suite start
=== RUN   TestTitleWords
--- PASS: TestTitleWords (0.00s)
=== RUN   TestTitleWordsFatalStops
--- PASS: TestTitleWordsFatalStops (0.00s)
PASS
teardown: suite end

More examples

Parallel top-level tests (t.Parallel) on independent pure functions.

// parallel_test.go
package main

import "testing"

func TestTitleHello(t *testing.T) {
    t.Parallel()
    if TitleWords("hello") != "Hello" {
        t.Fatal("bad title")
    }
}

func TestTitleWorld(t *testing.T) {
    t.Parallel()
    if TitleWords("world") != "World" {
        t.Fatal("bad title")
    }
}
go test -v -parallel 4

Runnable example

Save these two files in a directory (for example mathx/). Then:

cd mathx
go mod init example/mathx
go test -v
go test -cover

mathx.go:

package mathx

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

func Div(a, b int) (int, error) {
    if b == 0 {
        return 0, errDivZero
    }
    return a / b, nil
}

var errDivZero = errString("division by zero")

type errString string

func (e errString) Error() string { return string(e) }

mathx_test.go:

package mathx

import "testing"

func assertEqual(t *testing.T, got, want int) {
    t.Helper()
    if got != want {
        t.Errorf("got %d, want %d", got, want)
    }
}

func TestAdd(t *testing.T) {
    assertEqual(t, Add(2, 3), 5)
    assertEqual(t, Add(-1, 1), 0)
}

func TestDiv(t *testing.T) {
    got, err := Div(10, 2)
    if err != nil {
        t.Fatalf("unexpected err: %v", err)
    }
    assertEqual(t, got, 5)

    _, err = Div(1, 0)
    if err == nil {
        t.Fatal("expected error for divide by zero")
    }
}

func TestAddParallel(t *testing.T) {
    t.Parallel()
    assertEqual(t, Add(10, 5), 15)
}

Expected output: (verbose)

=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
=== RUN   TestDiv
--- PASS: TestDiv (0.00s)
=== RUN   TestAddParallel
--- PASS: TestAddParallel (0.00s)
PASS
ok      example/mathx   0.00xs

What to notice: Tests live in *_test.go, use TestXxx(*testing.T), and fail with t.Error / t.Fatal. t.Helper() makes failure lines point at the call site, not the helper body.

Try next: Add go test -run TestDiv -v; introduce a deliberate bug in Add and watch the assertion fail; try t.Error vs t.Fatal after a failed check.