π§ͺ Go Testing: Your Codeβs Safety Net
Imagine youβre a chef about to serve a dish. Before sending it out, you taste it first β thatβs testing! In Go, testing is like having a team of taste-testers who check your code before it goes live.
π― The Big Picture
Testing in Go is built right in. No extra tools needed. Itβs like Go comes with a recipe-tester included!
Our Analogy: Think of your code as a toy factory. Every toy (function) needs quality control (testing) before it leaves the factory. The testing package is your quality control team.
π¦ Meet testing.T β Your Test Helper
testing.T is like your clipboard-holding inspector. It helps you:
- Report problems β
- Log messages π
- Mark tests as failed or passed β
func TestAdd(t *testing.T) {
result := Add(2, 3)
if result != 5 {
t.Errorf("Got %d, want 5", result)
}
}
Key Methods of testing.T
| Method | What It Does |
|---|---|
t.Error() |
Reports failure, keeps going |
t.Errorf() |
Like Error, but with formatting |
t.Fatal() |
Reports failure, STOPS test |
t.Log() |
Prints a message |
t.Skip() |
Skips the test |
π¨ Test Patterns β The Right Way to Test
Pattern 1: Table-Driven Tests
Instead of writing 10 similar tests, put all cases in a table!
func TestMultiply(t *testing.T) {
tests := []struct {
a, b, want int
}{
{2, 3, 6},
{0, 5, 0},
{-1, 4, -4},
}
for _, tt := range tests {
got := Multiply(tt.a, tt.b)
if got != tt.want {
t.Errorf("Multiply(%d,%d) = %d; want %d",
tt.a, tt.b, got, tt.want)
}
}
}
Why? One test function, many cases. Clean and easy to add more!
Pattern 2: Subtests with t.Run()
Give each case a name β makes debugging easier!
func TestDivide(t *testing.T) {
tests := map[string]struct {
a, b, want int
}{
"positive": {10, 2, 5},
"negative": {-10, 2, -5},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
got := Divide(tt.a, tt.b)
if got != tt.want {
t.Errorf("got %d, want %d", got, tt.want)
}
})
}
}
β‘ Parallel Tests β Speed Things Up!
Want tests to run at the same time? Use t.Parallel()!
func TestSlowOperation(t *testing.T) {
t.Parallel() // This test can run alongside others
result := SlowOperation()
if result != expected {
t.Error("Failed!")
}
}
graph TD A["Start Tests"] --> B["Test A"] A --> C["Test B"] A --> D["Test C"] B --> E["All Done!"] C --> E D --> E
β οΈ Warning: Parallel tests should NOT share data that changes!
π The go test Command
Your magic spell to run tests!
Basic Commands
# Run all tests in current folder
go test
# Run with details (verbose)
go test -v
# Run a specific test
go test -run TestAdd
# Run tests in all subfolders
go test ./...
Helpful Flags
| Flag | What It Does |
|---|---|
-v |
Show all test names and output |
-run Name |
Run only matching tests |
-count=1 |
Disable test caching |
-timeout 30s |
Set max time for tests |
-short |
Skip long-running tests |
π Test Coverage β Did You Test Everything?
Coverage tells you: What percentage of your code has tests?
# See coverage percentage
go test -cover
# Generate detailed report
go test -coverprofile=coverage.out
go tool cover -html=coverage.out
Output example:
PASS
coverage: 85.7% of statements
graph TD A["Your Code"] --> B{Tested?} B -->|Yes| C["β Covered"] B -->|No| D["β Not Covered"] C --> E["85% Coverage"] D --> E
Goal: Aim for 80%+ coverage, but 100% isnβt always needed!
β±οΈ Benchmark Tests β How Fast Is Your Code?
Benchmarks measure speed and memory usage.
Meet testing.B
Just like testing.T is for tests, testing.B is for benchmarks!
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(2, 3)
}
}
The magic b.N: Go automatically finds the right number of iterations!
Running Benchmarks
# Run all benchmarks
go test -bench=.
# Run with memory stats
go test -bench=. -benchmem
Output example:
BenchmarkAdd-8 1000000000 0.29 ns/op 0 B/op 0 allocs/op
| Part | Meaning |
|---|---|
1000000000 |
Iterations run |
0.29 ns/op |
Time per operation |
0 B/op |
Memory per operation |
0 allocs/op |
Memory allocations |
Key testing.B Methods
| Method | What It Does |
|---|---|
b.ResetTimer() |
Restart the clock (after setup) |
b.StopTimer() |
Pause timing |
b.StartTimer() |
Resume timing |
b.ReportAllocs() |
Include memory stats |
func BenchmarkWithSetup(b *testing.B) {
// Setup (not timed)
data := prepareData()
b.ResetTimer() // Start timing NOW
for i := 0; i < b.N; i++ {
ProcessData(data)
}
}
π Example Tests β Documentation That Tests Itself!
Example tests show how to use your code AND verify it works!
func ExampleAdd() {
result := Add(2, 3)
fmt.Println(result)
// Output: 5
}
Magic: The // Output: comment tells Go what to expect!
Rules for Example Tests
- Function name starts with
Example - Must have
// Output:comment - Uses
fmt.Printlnto show results
func ExampleGreet() {
message := Greet("World")
fmt.Println(message)
// Output: Hello, World!
}
func ExampleCalculator_Add() {
calc := NewCalculator()
fmt.Println(calc.Add(1, 2))
// Output: 3
}
Unordered Output
When order doesnβt matter:
func ExampleGetColors() {
colors := GetColors()
for _, c := range colors {
fmt.Println(c)
}
// Unordered output:
// red
// blue
// green
}
πΊοΈ The Complete Testing Picture
graph TD A["Write Code"] --> B["Write Tests"] B --> C["go test"] C --> D{Pass?} D -->|No| E["Fix Code"] E --> C D -->|Yes| F["Check Coverage"] F --> G["Run Benchmarks"] G --> H["β Ship It!"]
π Quick Reference
| Task | Command/Code |
|---|---|
| Run tests | go test |
| Verbose output | go test -v |
| Check coverage | go test -cover |
| Run benchmarks | go test -bench=. |
| Run specific test | go test -run TestName |
| Create test file | *_test.go |
| Test function | func TestXxx(t *testing.T) |
| Benchmark function | func BenchmarkXxx(b *testing.B) |
| Example function | func ExampleXxx() |
π You Did It!
Now you know:
- β
How
testing.Thelps report test results - β Table-driven tests and subtests patterns
- β
Running tests in parallel with
t.Parallel() - β
All the
go testcommand tricks - β Measuring code coverage
- β
Writing benchmarks with
testing.B - β Creating self-documenting example tests
Your code is now protected by its very own quality control team! πβ¨
