Testing
Go ships its test framework in the standard library. No plugins, no assertion
DSL — just the testing package and a naming convention. Here's a complete
test file:
package main
import "testing"
func TestDouble(t *testing.T) {
got := double(3)
if got != 6 {
t.Errorf("double(3) = %d, want 6", got)
}
}
The rules of the game
- Tests live in files ending in
_test.go, in the same package as the code they test. The compiler excludes them from normal builds. - A test is any function named
TestXxxthat takes exactly one argument,t *testing.T. Thego testcommand finds and runs them all. - There are no assertions. You write ordinary Go — an
ifand a call to a method ontwhen something is wrong.
The two methods you'll use most: t.Errorf reports a failure and keeps
going, so one run can reveal several problems. t.Fatalf reports a failure
and stops this test immediately — use it when continuing makes no sense,
like when a setup step failed and every later check would just panic.
Table-driven tests
Testing one input is rarely enough, and copy-pasting the if block for each
case gets old fast. The Go idiom — you will see it in virtually every real
codebase — is a table: a slice of anonymous structs holding inputs and
expected outputs, and one loop that checks them all.
func TestDouble(t *testing.T) {
tests := []struct {
name string
in int
want int
}{
{"zero", 0, 0},
{"positive", 3, 6},
{"negative", -2, -4},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := double(tt.in); got != tt.want {
t.Errorf("double(%d) = %d, want %d", tt.in, got, tt.want)
}
})
}
}
t.Run starts a named subtest for each row, so failures read like
TestDouble/negative and point straight at the broken case. Adding coverage
becomes a one-line edit: add a row.
Running tests
go test runs every test in the current package and prints ok or a failure
report. Three flags earn their keep immediately:
go test -v— verbose: list every test and subtest as it runs, pass or fail.go test -run 'Double/negative'— run only tests (and subtests) whose names match a regular expression. Invaluable when you're chasing one failure.go test -count=1— force a re-run instead of serving cached results. (Go caches passing test results per package;-count=1says "run it anyway".)
Tests are the spec
Here's the part that makes this lesson different from the others: every
exercise in this course is graded by exactly this kind of test. When you
press run, a hidden _test.go file compiles alongside your code and go test
decides pass or fail.
That means the skill of reading a table-driven test is the skill of reading a specification. Each row is a promise: this input produces that output. Edge cases you might forget — empty input, negatives, boundaries — sit right there in the table.
The exercise below makes this explicit. The prompt shows you the actual test that grades your solution — your job is to read the table like a spec and write the function it describes.
Practice
1. FizzBuzz, Spec Included
IntermediateImplement the classic FizzBuzz as a function:
func fizzBuzz(n int) string- If
nis divisible by both 3 and 5, return"FizzBuzz". - If
nis divisible by 3 only, return"Fizz". - If
nis divisible by 5 only, return"Buzz". - Otherwise, return
nas a decimal string (hint:strconv.Itoa).
This time there's no mystery about the grading: below is the exact test that will run against your code. Read the table like a spec — every row is one requirement.
func TestFizzBuzz(t *testing.T) { tests := []struct { name string n int want string }{ {"one", 1, "1"}, {"two", 2, "2"}, {"three", 3, "Fizz"}, {"five", 5, "Buzz"}, {"six", 6, "Fizz"}, {"ten", 10, "Buzz"}, {"fifteen", 15, "FizzBuzz"}, {"thirty", 30, "FizzBuzz"}, {"seven", 7, "7"}, {"ninety-eight", 98, "98"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := fizzBuzz(tt.n); got != tt.want { t.Errorf("fizzBuzz(%d) = %q, want %q", tt.n, got, tt.want) } }) } }Notice what the table tells you that the prose above almost hides: 15 must be
"FizzBuzz"— not"Fizz"— so check the divisible-by-both case first.package main import "fmt" // fizzBuzz returns "Fizz" for multiples of 3, "Buzz" for multiples of 5, // "FizzBuzz" for multiples of both, and n as a string otherwise. func fizzBuzz(n int) string { // TODO: implement return "" } func main() { for n := 1; n <= 15; n++ { fmt.Println(fizzBuzz(n)) } }Sign in to write and run your solution in the browser.
Sign in for hints and the solution.
- If