Go Unit Testing
Testing is built directly into Go — no external framework is needed. The testing package provides everything required to write, run, and measure tests. Go's testing conventions are simple: test files end in _test.go, test functions start with Test, and the go test command runs them all.
Testing File Structure
myapp/
├── math.go ← the code to test
└── math_test.go ← the test file
Test files must be in the same package as the code being tested (or a _test suffix package for black-box testing).
The Code to Test
File: math.go
package math
func Add(a, b int) int {
return a + b
}
func Subtract(a, b int) int {
return a - b
}
func Divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("cannot divide by zero")
}
return a / b, nil
}
Writing Tests
File: math_test.go
package math
import "testing"
func TestAdd(t *testing.T) {
result := Add(2, 3)
expected := 5
if result != expected {
t.Errorf("Add(2, 3) = %d; want %d", result, expected)
}
}
func TestSubtract(t *testing.T) {
result := Subtract(10, 4)
expected := 6
if result != expected {
t.Errorf("Subtract(10, 4) = %d; want %d", result, expected)
}
}
Test Function Anatomy
func TestAdd(t *testing.T) {
│ │ │
│ │ └── testing.T provides methods to report failure
│ └──────────── must start with "Test" followed by capital letter
└─────────────────── func keyword
t.Errorf("message") → marks test as failed, continues running
t.Fatalf("message") → marks test as failed, stops this test immediately
t.Logf("message") → logs a message (only shown on failure)
Running Tests
# Run all tests in current package
go test
# Run with verbose output (shows each test name)
go test -v
# Run a specific test by name
go test -run TestAdd
# Run tests in all packages
go test ./...
Passing output:
--- PASS: TestAdd (0.00s)
--- PASS: TestSubtract (0.00s)
PASS
ok myapp/math 0.002s
Table-Driven Tests
Table-driven tests run the same function with many different inputs in a clean, compact format. This is the standard Go pattern for thorough testing.
package math
import "testing"
func TestAddTableDriven(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"positive numbers", 2, 3, 5},
{"zero plus number", 0, 5, 5},
{"negative numbers", -3, -2, -5},
{"mixed signs", -3, 7, 4},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
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)
}
})
}
}
Output with go test -v:
--- PASS: TestAddTableDriven (0.00s)
--- PASS: TestAddTableDriven/positive_numbers (0.00s)
--- PASS: TestAddTableDriven/zero_plus_number (0.00s)
--- PASS: TestAddTableDriven/negative_numbers (0.00s)
--- PASS: TestAddTableDriven/mixed_signs (0.00s)
Testing Error Cases
package math
import (
"errors"
"testing"
)
func TestDivideByZero(t *testing.T) {
_, err := Divide(10, 0)
if err == nil {
t.Error("expected an error when dividing by zero, got nil")
}
}
func TestDivideNormal(t *testing.T) {
result, err := Divide(10, 2)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != 5 {
t.Errorf("Divide(10, 2) = %v; want 5", result)
}
}
Benchmark Tests
Benchmark functions measure how fast a function runs. They start with Benchmark and receive *testing.B.
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(100, 200)
}
}
# Run benchmarks
go test -bench=.
Output:
BenchmarkAdd-8 1000000000 0.234 ns/op
Test Coverage
Check what percentage of code is covered by tests.
# Show coverage percentage
go test -cover
# Generate a visual HTML coverage report
go test -coverprofile=coverage.out
go tool cover -html=coverage.out
Testing Best Practices
| Practice | Why It Matters |
|---|---|
| Use table-driven tests | Test many inputs without repeating test logic |
| Test error paths explicitly | Confirm functions fail correctly, not just succeed |
| Keep tests independent | Each test should pass or fail on its own |
| Use descriptive test names | Failures clearly show which case broke |
Run go test ./... before committing | Catches regressions across the whole project |
Key Points
- Test files end in
_test.goand test functions start withTest - Run tests with
go test; use-vfor detailed output - Use
t.Errorfto fail and continue; uset.Fatalfto fail and stop - Table-driven tests are the standard Go pattern for testing multiple inputs
- Use
go test -coverto measure test coverage across the package - Benchmark functions measure performance using
*testing.Bandgo test -bench=.
