Skip to content

Go Advanced Testing — Table-Driven Tests, Fuzzing, Test Coverage, and Benchmarks

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Go Advanced Testing. We cover key concepts, practical examples, and best practices to help you master this topic.

Go advanced testing includes table-driven tests, fuzzing with testing.F, test coverage analysis, and subtest/subbenchmark organization.

What You'll Learn

  • Table-driven test patterns
  • Fuzz testing
  • Test coverage and profiling
  • Subtests and test helpers

Why It Matters

Advanced testing ensures robust software. Docker uses table-driven tests extensively. Kubernetes uses fuzzing for security. DodaZIP uses coverage analysis for quality gates.

Real-World Use

CI/CD quality gates, security vulnerability discovery, regression prevention, API Contract Testing.

flowchart LR
    A["Advanced Testing"] --> B["Table-Driven"]
    A --> C["Fuzzing"]
    A --> D["Coverage"]
    A --> E["Subtests"]
    A:::current --> B
    style A fill:#2563eb,stroke:#2563eb,color:#fff
    style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b

Table-Driven Tests

func TestDivide(t *testing.T) {
    tests := []struct {
        name string
        a, b float64
        want float64
        err  bool
    }{
        {name: "positive", a: 10, b: 2, want: 5},
        {name: "negative", a: -6, b: 3, want: -2},
        {name: "zero division", a: 5, b: 0, want: 0, err: true},
        {name: "fraction", a: 3, b: 2, want: 1.5},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := divide(tt.a, tt.b)
            if tt.err && err == nil {
                t.Error("expected error, got none")
            }
            if !tt.err && err != nil {
                t.Errorf("unexpected error: %v", err)
            }
            if got != tt.want {
                t.Errorf("divide(%f, %f) = %f, want %f", tt.a, tt.b, got, tt.want)
            }
        })
    }
}

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

Fuzz Testing

func FuzzHex(f *testing.F) {
    seeds := []string{"hello", "world", "123", ""}
    for _, s := range seeds {
        f.Add(s)
    }

    f.Fuzz(func(t *testing.T, input string) {
        encoded := hex.EncodeToString([]byte(input))
        decoded, err := hex.DecodeString(encoded)
        if err != nil {
            t.Errorf("round trip failed: %v", err)
        }
        if string(decoded) != input {
            t.Error("round trip mismatch")
        }
    })
}

Test Coverage

go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html
go tool cover -func=coverage.out
// In CI
func TestMain(m *testing.M) {
    c := m.Run()
    // Fail if coverage below threshold
    if c := testing.CoverMode(); c != "" {
        // Parse coverage output
    }
    os.Exit(c)
}

Test Helper Functions

func TestUserValidation(t *testing.T) {
    t.Helper()
    assert := func(t *testing.T, got, want interface{}) {
        t.Helper()
        if got != want {
            t.Errorf("got %v, want %v", got, want)
        }
    }

    user := &User{Name: "Alice", Email: "alice@test.com"}
    assert(t, user.Name, "Alice")
    assert(t, user.Email, "alice@test.com")
}

Golden Files

func TestGoldenFile(t *testing.T) {
    input := "hello"
    got := process(input)

    golden := filepath.Join("testdata", t.Name()+".golden")
    if *update {
        os.WriteFile(golden, []byte(got), 0644)
    }

    want, _ := os.ReadFile(golden)
    if got != string(want) {
        t.Errorf("got %s, want %s", got, want)
    }
}

Common Mistakes

1. Not Running Subtests in Parallel

for _, tt := range tests {
    tt := tt  // Capture range variable
    t.Run(tt.name, func(t *testing.T) {
        t.Parallel()  // Run subtests concurrently
        // test body
    })
}

2. Forgetting go vet

// Always run: go vet ./... before commits

3. Skipping Error Checks in Tests

Don't use _ for errors in tests. Always assert errors.

4. Flaky Tests

Avoid time.Sleep for synchronization. Use retry or deterministic timing.

5. Not Using -race

Always run go test -race ./... to detect data races.

Practice Questions

1. What is the benefit of table-driven tests? Clear test cases, easy to add new cases, reduces boilerplate, explicit naming.

2. How does fuzzing find bugs? Generates random inputs automatically. Tests edge cases developers might miss. Finds panics and logic errors.

3. What is test coverage? Percentage of code statements executed during tests. Use go test -cover to measure. 80%+ is a common target.

4. What are subtests? Nested tests with t.Run. Each subtest can run independently. Supports parallel execution and selective running.

Challenge: Write a table-driven test for a string reverse function.

Solution
func TestReverse(t *testing.T) {
    tests := []struct {
        name string
        input string
        want string
    }{
        {name: "simple", input: "hello", want: "olleh"},
        {name: "unicode", input: "你好", want: "好你"},
        {name: "palindrome", input: "racecar", want: "racecar"},
        {name: "empty", input: "", want: ""},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if got := Reverse(tt.input); got != tt.want {
                t.Errorf("Reverse() = %q, want %q", got, tt.want)
            }
        })
    }
}

FAQ

{{< faq question="How do I run specific tests?" >}} go test -run TestName/SubtestName runs matching tests. Use -v for verbose output. -count=1 disables Caching. {{< /faq >}}

{{< faq question="What is the difference between Error and Fatal?" >}} t.Error marks test as failed but continues. t.Fatal marks as failed and stops the test immediately. {{< /faq >}}

{{< faq question="How do I test HTTP handlers?" >}} Use httptest.NewServer or httptest.NewRecorder. They provide in-memory HTTP test infrastructure. {{< /faq >}}

{{< faq question="What is go-cmp?" >}} A package for comparing values in tests. Provides readable diffs: cmp.Diff(want, got). Better than reflect.DeepEqual. {{< /faq >}}

{{< faq question="How do I set up test fixtures?" >}} Use testdata directory for files. Use t.TempDir() for temporary directories — automatically cleaned up. {{< /faq >}}

Try It Yourself

package main

import (
    "testing"
)

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

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

Run with:

go test -v

Expected output — PASS with test result.

What's Next

Now that you understand advanced testing, explore benchmarking and profiling.

Topic Description Link
Go Benchmarking Performance Testing {{< ref "35-benchmarking" >}}
Go Profiling Performance profiling {{< ref "36-profiling" >}}
Go Modules Package management {{< ref "37-modules" >}}

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro

Home Browse Go