go test CLI. You don’t need external test frameworks like Jest or JUnit to write unit tests, benchmarks, or fuzzers.TL;DR (Quick Summary)#
- Test Files: Named
*_test.goalongside the code file being tested. - Unit Tests (
TestXxx(t *testing.T)): Standard unit tests. Failures called viat.Errorfort.Fatalf. - Table-Driven Tests: Idiomatic Go testing pattern iterating over a slice of test case structs.
- Benchmarks (
BenchmarkXxx(b *testing.B)): Performance testing loop runningb.Niterations. - Fuzzing (
FuzzXxx(f *testing.F)): Automated random payload generation for security testing.
1. Table-Driven Unit Testing Pattern#
Table-Driven Tests keep test cases clean, maintainable, and readable.
Create calculator_test.go:
package main
import "testing"
func Add(a, b int) int {
return a + b
}
func TestAddTableDriven(t *testing.T) {
// Define table of test cases
tests := []struct {
name string
a, b int
expected int
}{
{name: "Positive numbers", a: 2, b: 3, expected: 5},
{name: "Negative numbers", a: -1, b: -4, expected: -5},
{name: "Zero addition", a: 5, b: 0, expected: 5},
}
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; expected %d", tt.a, tt.b, result, tt.expected)
}
})
}
}Run tests with verbose output and coverage:
go test -v -cover ./...2. Benchmarking Performance (testing.B)#
Benchmark functions measure code execution time and memory allocation per operation.
Add to calculator_test.go:
func BenchmarkAdd(b *testing.B) {
// b.N is dynamically adjusted by the Go test runner until accurate metrics are achieved
for i := 0; i < b.N; i++ {
Add(100, 200)
}
}Run benchmarks with memory statistics:
go test -bench=. -benchmemExpected Terminal Output:
goos: linux
goarch: amd64
pkg: github.com/username/go-masterclass
cpu: 11th Gen Intel(R) Core(TM) i7-1185G7 @ 3.00GHz
BenchmarkAdd-8 1000000000 0.283 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/username/go-masterclass 0.345sNotice: 0 B/op, 0 allocs/op proves Add allocates zero heap memory!
3. Automated Fuzz Testing (testing.F)#
Fuzz testing feeds random binary and string data to your functions to find edge-case crashes.
func Reverse(s string) string {
runes := []rune(s)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
return string(runes)
}
func FuzzReverse(f *testing.F) {
testcases := []string{"Hello", "world", " ", "!123"}
for _, tc := range testcases {
f.Add(tc) // Seed corpus
}
f.Fuzz(func(t *testing.T, orig string) {
rev := Reverse(orig)
doubleRev := Reverse(rev)
if orig != doubleRev {
t.Errorf("Before: %q, after double reverse: %q", orig, doubleRev)
}
})
}Run fuzzing for 10 seconds:
go test -fuzz=FuzzReverse -fuzztime=10s4. Troubleshooting & Common Errors#
Error 1: PASS Output Missing Failing Assertion#
The Cause: Calling t.Log instead of t.Errorf or t.Fatalf.
The Fix: Use t.Errorf to record failure without stopping other subtests, or t.Fatalf to stop execution immediately.
Summary & Next Steps#
In this episode:
- We wrote Table-Driven unit tests with
testing.T. - We benchmarked performance and heap allocations with
testing.B. - We discovered edge cases using automated fuzzing (
testing.F).
In Episode 15: Building a Production REST API, we will assemble our final production web service!

