Mastering Go Testing: From Basics to Subtests in 3 Minutes

Published: (February 11, 2026 at 03:54 PM EST)
2 min read
Source: Dev.to

Source: Dev.to

Level 1: The Basic Test

A test in Go is just a function that starts with Test and takes a *testing.T.

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    expected := 5

    if result != expected {
        t.Errorf("Add(2, 3) = %d; want %d", result, expected)
    }
}

We don’t need external assertion libraries—simple if statements and t.Errorf are enough.

Level 2: Table‑Driven Tests

Copy‑pasting the same test for different inputs is tedious and error‑prone.
Table‑driven tests let you define a slice of test cases and iterate over them.

func TestAdd(t *testing.T) {
    tests := []struct {
        a, b     int
        expected int
    }{
        {2, 3, 5},
        {0, 0, 0},
        {-1, 1, 0},
    }

    for _, tt := range tests {
        result := Add(tt.a, tt.b)
        if result != tt.expected {
            t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected)
        }
    }
}

Adding a new case is just another line in the slice.

Level 3: Subtests with t.Run

Subtests give each case a distinct name, improving output and debugging.

func TestAdd(t *testing.T) {
    tests := []struct {
        name     string // New!
        a, b     int
        expected int
    }{
        {"positive numbers", 2, 3, 5},
        {"zeros", 0, 0, 0},
        {"negative", -1, 1, 0},
    }

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

Why is this better?

  • Clear output / easier debugging – failures show the subtest name, e.g. TestAdd/positive numbers.

  • Granular control – run a single subtest from the command line:

    go test -run TestAdd/zeros
  • Parallelism – invoke t.Parallel() inside the subtest to run cases concurrently.

Summary

  • Basics: Use t.Errorf to report failures.
  • Table‑driven: Organize test data in a []struct.
  • Subtests: Use t.Run for better reporting, selective execution, and parallelism.

Happy testing!

0 views
Back to Blog

Related posts

Read more »

A Beginner’s Guide to Testing in Go

Tests Enforce Contracts Now right off the bat, you don’t want to get this wrong. When writing tests, the key thing to keep in mind is that we should treat what...

Savior: Low-Level Design

Grinding Go: Low‑Level Design I went back to the drawing board for interview preparation and to sharpen my problem‑solving skills. Software development is in a...