Lesson 18 · 18 of 20
Table-driven checks · Go
Check a Go function with a table of cases, just like go test does: each case with a name, an input and an expected result.

The most common way to test a function in Go is a table of cases: a slice of structs with a name, the input and the expected result. tests is a slice of an anonymous struct, declared right there because it is used nowhere else.
The function under test is shipping: orders of 50.00 EUR or more ship free and the rest pay 4.95. Amounts are in cents (5000 is 50.00 EUR) so you compare integers rather than decimals, which may not match because of rounding.
The loop calls shipping with each total, compares what it got (got) with what it expected (want) and prints ok or FAIL. On a failure the message says what was called, what came out and what should have: that is usually enough to find the bug without a debugger. failed counts the failures for the final summary.
And there is one: exactly 50.00 expects 0 but gets 495, because the condition is total > 5000 and should be >=. It is the classic bug, the boundary one, which is why you should always test the edges: one cent short (4999), the limit itself (5000) and the empty case (0).
In a project, this same table goes into a shipping_test.go file, inside func TestShipping(t *testing.T); each case runs with t.Run(tc.name, ...), failures are reported with t.Errorf, and the whole thing runs with go test. Adding a case means adding one line.
Code · main.go
package mainimport "fmt"// shipping returns the delivery cost in cents: orders from 50.00 ship free.func shipping(total int) int { if total > 5000 { // bug: an order of exactly 50.00 should also ship free return 0 } return 495}func main() { tests := []struct { name string total, want int // in cents }{ {"small order", 2000, 495}, {"one cent short", 4999, 495}, {"exactly 50.00", 5000, 0}, {"large order", 12000, 0}, {"empty cart", 0, 495}, } failed := 0 for _, tc := range tests { got := shipping(tc.total) status := "ok " if got != tc.want { status, failed = "FAIL", failed+1 } fmt.Printf("%s %-15s shipping(%5d) = %3d, want %3d\n", status, tc.name, tc.total, got, tc.want) } fmt.Printf("%d passed, %d failed\n", len(tests)-failed, failed)}Output · go run main.go
ok small order shipping( 2000) = 495, want 495 ok one cent short shipping( 4999) = 495, want 495 FAIL exactly 50.00 shipping( 5000) = 495, want 0 ok large order shipping(12000) = 0, want 0 ok empty cart shipping( 0) = 495, want 495 4 passed, 1 failed
Key points
- A table of cases is a slice of structs: name, input and expected.
- Compare
gotwithwant, and show both when it fails. - Test the edges: the limit itself, one below and the empty case.
- With money, compare whole cents.
- In
go test, the same table witht.Runandt.Errorf.
Exercise
Fix the bug by changing > to >= and check that all five cases pass. Then add an express bool parameter that always adds 6.00 EUR, and extend the table with two express cases.