go-testing
Go Testing
The testing package provides support for automated testing of Go packages. Tests are run with go test and require no external libraries — the standard library covers unit tests, benchmarks, fuzz tests, and example functions.
File and Function Conventions
Test files must end in _test.go. They are excluded from normal builds but included by go test.
Test functions must match the signature func TestXxx(*testing.T) where Xxx does not start with a lowercase letter:
package mypackage
import "testing"
func TestAdd(t *testing.T) {
got := Add(2, 3)
if got != 5 {
t.Errorf("Add(2, 3) = %d; want 5", got)
}
}
White-box vs Black-box Tests
- Same package (
package mypackage) — accesses unexported identifiers _testsuffix package (package mypackage_test) — tests only the exported API; this is "black-box" testing
Both styles can coexist in the same directory.
Reporting Failures
| Method | Behaviour |
|---|---|
t.Errorf(format, args...) |
Marks failed, continues execution |
t.Error(args...) |
Marks failed, continues execution |
t.Fatalf(format, args...) |
Marks failed, stops test immediately |
t.Fatal(args...) |
Marks failed, stops test immediately |
t.Fail() |
Marks failed without logging, continues |
t.FailNow() |
Marks failed without logging, stops immediately |
Use Errorf/Error when checking multiple independent conditions so all failures are reported. Use Fatalf/Fatal when a failure makes further checks meaningless (e.g., a nil pointer).
Prefer t.Errorf over t.Fatalf unless subsequent assertions depend on a prior one succeeding.
Table-Driven Tests
Table-driven tests are the idiomatic Go pattern for testing a function against many inputs:
func TestDivide(t *testing.T) {
tests := []struct {
name string
a, b float64
want float64
wantErr bool
}{
{name: "positive", a: 10, b: 2, want: 5},
{name: "negative divisor", a: 10, b: -2, want: -5},
{name: "divide by zero", a: 10, b: 0, wantErr: true},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got, err := Divide(tc.a, tc.b)
if (err != nil) != tc.wantErr {
t.Fatalf("Divide(%v, %v) error = %v, wantErr %v", tc.a, tc.b, err, tc.wantErr)
}
if !tc.wantErr && got != tc.want {
t.Errorf("Divide(%v, %v) = %v; want %v", tc.a, tc.b, got, tc.want)
}
})
}
}
Name each case descriptively. Use t.Run so each case appears as a named subtest in output and can be run individually.
Subtests
t.Run(name, func(t *testing.T)) creates a named subtest. Subtests:
- Appear in output as
TestParent/SubName - Can be run individually:
go test -run TestParent/SubName - Share setup/teardown with the parent
- Can be run in parallel independently of other top-level tests
func TestAPI(t *testing.T) {
server := startTestServer(t) // shared setup
t.Run("GET /users", func(t *testing.T) {
// ...
})
t.Run("POST /users", func(t *testing.T) {
// ...
})
// server is cleaned up after all subtests finish
}
Parallel Tests
Call t.Parallel() at the start of a test function to allow it to run concurrently with other parallel tests:
func TestExpensive(t *testing.T) {
t.Parallel()
// ...
}
For parallel subtests in a table-driven test, capture the loop variable:
for _, tc := range tests {
tc := tc // capture range variable (required before Go 1.22)
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
// use tc safely
})
}
From Go 1.22 onward, loop variable capture is automatic and the tc := tc line is no longer needed.
Cleanup
t.Cleanup(f func()) registers a function to run after the test (and all its subtests) complete. Cleanup functions run in LIFO order.
func TestWithDB(t *testing.T) {
db := openTestDB(t)
t.Cleanup(func() { db.Close() })
// test body — db.Close is called automatically when test ends
}
Prefer t.Cleanup over defer inside test helpers because it runs after all subtests complete, not just when the helper function returns.
Test Helpers
Mark a function as a test helper with t.Helper() so error output points to the call site, not inside the helper:
func assertEqualInts(t *testing.T, got, want int) {
t.Helper() // makes error line point to the caller
if got != want {
t.Errorf("got %d; want %d", got, want)
}
}
Always call t.Helper() as the first statement in helper functions.
Temporary Directories
t.TempDir() creates a temporary directory that is automatically removed when the test completes:
func TestWriteFile(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "output.txt")
// write to path — dir is cleaned up automatically
}
Environment Variables
t.Setenv(key, value) sets an environment variable and restores the original value after the test. Cannot be used in parallel tests.
func TestWithEnv(t *testing.T) {
t.Setenv("MY_CONFIG", "test-value")
// original value restored after test
}
Skipping Tests
Skip a test conditionally using t.Skip, t.Skipf, or t.SkipNow:
func TestIntegration(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test in short mode")
}
// ...
}
func TestRequiresDocker(t *testing.T) {
if os.Getenv("DOCKER_HOST") == "" {
t.Skip("DOCKER_HOST not set")
}
// ...
}
Example Functions
Example functions serve as documentation and are verified by go test:
func ExampleAdd() {
fmt.Println(Add(1, 2))
// Output: 3
}
The // Output: comment is compared against stdout. Examples without an output comment are compiled but not executed. Use // Unordered output: when output order is non-deterministic.
Naming conventions:
func Example() { ... } // package example
func ExampleAdd() { ... } // function Add
func ExampleCalc() { ... } // type Calc
func ExampleCalc_Add() { ... } // method Calc.Add
func ExampleAdd_second() { ... } // second example for Add (suffix starts lowercase)
TestMain
TestMain controls global test setup and teardown. Define it in any _test.go file in the package:
func TestMain(m *testing.M) {
// setup
code := m.Run()
// teardown
os.Exit(code)
}
Use TestMain for package-level resources (database connections, server processes). It is not necessary for per-test resources — use t.Cleanup instead.
Running Tests
# Run all tests in the current module
go test ./...
# Run with verbose output
go test -v ./...
# Run tests matching a pattern (regexp)
go test -run TestAdd ./...
# Run a specific subtest
go test -run TestDivide/divide_by_zero ./...
# Run with race detector
go test -race ./...
# Run with coverage
go test -cover ./...
# Generate HTML coverage report
go test -coverprofile=coverage.out ./... && go tool cover -html=coverage.out
# Skip slow tests
go test -short ./...
# Set test timeout
go test -timeout 30s ./...
# Run benchmarks
go test -bench=. ./...
# Run benchmarks with memory allocation stats
go test -bench=. -benchmem ./...
Quick Reference: testing.T Methods
| Method | Purpose |
|---|---|
t.Run(name, f) |
Create named subtest |
t.Parallel() |
Mark test as parallel |
t.Helper() |
Mark as helper function |
t.Cleanup(f) |
Register teardown function |
t.TempDir() |
Create auto-cleaned temp directory |
t.Setenv(k, v) |
Set env var, auto-restored after test |
t.Chdir(dir) |
Change working dir, auto-restored |
t.Context() |
Context canceled before cleanup runs |
t.Log(args...) |
Log (shown on failure or with -v) |
t.Logf(format, args...) |
Log formatted |
t.Error(args...) |
Fail + log, continue |
t.Errorf(format, args...) |
Fail + log formatted, continue |
t.Fatal(args...) |
Fail + log, stop |
t.Fatalf(format, args...) |
Fail + log formatted, stop |
t.Skip(args...) |
Skip + log, stop |
t.Skipf(format, args...) |
Skip + log formatted, stop |
t.Name() |
Return full test name |
t.Failed() |
Reports whether test has failed |
t.Deadline() |
Returns test deadline from -timeout flag |
Additional Resources
For benchmarks, fuzz testing, and advanced patterns:
references/benchmarks-and-fuzzing.md—testing.BAPI,b.Loop()style, parallel benchmarks,testing.Ffuzz tests,TestMainpatterns, andAllocsPerRun
More from the-perfect-developer/the-perfect-opencode
html
Apply Google HTML style guide conventions to HTML code
19turso-libsql
This skill should be used when the user asks to "connect to Turso", "use libSQL", "set up a Turso database", "query Turso with TypeScript", or needs guidance on Turso Cloud, embedded replicas, or vector search with libSQL.
11alpinejs
This skill should be used when the user asks to "add Alpine.js", "create Alpine component", "use Alpine directives", "build interactive UI with Alpine", or needs guidance on Alpine.js development patterns and best practices.
10python-dependency-injection
This skill should be used when the user asks to "implement dependency injection in Python", "use the dependency-injector library", "decouple Python components", "write testable Python services", or needs guidance on Inversion of Control, DI containers, provider types, and wiring in Python applications.
3copilot-sdk
This skill should be used when the user asks to "integrate GitHub Copilot into an app", "use the Copilot SDK", "build a Copilot-powered agent", "embed Copilot in a service", or needs guidance on the GitHub Copilot SDK for Python, TypeScript, Go, or .NET.
3conventional-git-commit
This skill MUST be loaded on every git commit without exception. It should also be used when the user asks to "write a conventional commit", "format a commit message", "follow conventional commits spec", "create a semantic commit", "make a commit", "commit changes", or "git commit". Every commit message produced in this project MUST conform to this specification.
3