Testing
Every chapter so far has tested code by reading fmt.Println output by eye — fine for a 5-line example, unworkable for a real program with dozens of functions. Go's standard library includes a testing package, and the go command includes a built-in test runner, go test — together, the standard way Go code gets verified automatically.
The File Naming Convention
A test file must be named *_test.go (here, math_test.go, testing code in math.go) — this naming is how Go's tooling recognises test code and excludes it from a normal build. Each test function's name must start with Test and take exactly one parameter, *testing.T.
Running Tests with go test
go test automatically finds and runs every Test... function in *_test.go files, with no manual registration needed anywhere. -v shows each individual test's name and result, rather than just a single pass/fail summary line.
t.Errorf vs t.Fatalf
t.Errorf records a failure but lets the rest of the test function keep running — useful when several independent checks are worth reporting together. t.Fatalf records a failure AND stops that test function immediately, appropriate when a later check would be meaningless or panic without the earlier one passing first (here, examining result wouldn't make sense if err turned out to be unexpectedly nil from a successful division).
Table-Driven Tests — The Idiomatic Go Pattern
Rather than writing a separate test function per case, a table-driven test declares a slice of anonymous structs (Intermediate Chapter 1's struct knowledge, combined with Fundamentals Chapter 6's range) — each one a distinct input/expected-output pair — and loops over them with one shared assertion. Adding a new test case becomes a one-line addition to the table, rather than an entirely new function.
Subtests with t.Run
t.Run("name", func(t *testing.T) { ... }) wraps each table entry as its own named subtest — go test -v then reports each case individually (TestAdd/2+3, TestAdd/-1+1, ...) instead of one combined pass/fail for the whole table, making it immediately clear which specific input failed.
go test -run TestAdd runs only tests whose name matches the given pattern — useful for focusing on one failing test without re-running an entire, possibly slow, test suite.
| Concept | Convention |
|---|---|
| Test file name | name_test.go |
| Test function name | func TestXxx(t *testing.T) |
| Record failure, continue | t.Errorf(...) |
| Record failure, stop this test | t.Fatalf(...) |
| Run all tests | go test ./... |
| Verbose output | go test -v ./... |
Coding Challenges
Write a function IsEven(n int) bool, then write a test file with TestIsEven covering at least 3 cases (an even number, an odd number, and zero) using t.Errorf for any mismatch.
📄 View solutionRewrite Challenge 1's test as a table-driven test: a slice of struct { input int; want bool } with at least 5 cases, looped over with a single shared assertion.
📄 View solutionWrite a function SafeDivide(a, b float64) (float64, error) returning an error for division by zero. Write a table-driven test using t.Run for named subtests, covering a normal division and a divide-by-zero case, checking both the result/error appropriately for each.
📄 View solutionChapter 4 Quick Reference
- name_test.go — required file naming for Go to recognise test code
- func TestXxx(t *testing.T) — required test function signature
- t.Errorf(...) — records a failure, test function keeps running
- t.Fatalf(...) — records a failure, stops the test function immediately
- Table-driven tests — a slice of struct cases looped with one shared assertion
- t.Run("name", func(t *testing.T) {...}) — named subtests, reported individually
- go test ./... / go test -v ./... — run all tests, verbosely
- This completes Go Advanced (Course 3) as currently planned. A possible Chapter 5 would cover JSON and encoding/json for working with APIs.