Module lessons (1/5)
The `testing` package
In Go, tests are part of the stdlib: no external library, no framework to configure. All you need is a *_test.go file next to the code, a few TestXxx functions, and the go test command.
Basic conventions
- Test files are named
<something>_test.goand live in the same package as the code they test (or in a<name>_testpackage for "black-box tests"). - Test functions have an exact signature:
func TestXxx(t *testing.T)whereXxxstarts with an uppercase letter. - You import
testingfrom the stdlib. - No
assertEquals: you useif got != want { t.Errorf(...) }withfmt-verbs.
// math.go
package math
func Sum(a, b int) int { return a + b }
// math_test.go
package math
import "testing"
func TestSum(t *testing.T) {
got := Sum(2, 3)
if got != 5 {
t.Errorf("Sum(2,3) = %d; voglio %d", got, 5)
}
}Run it with:
go test ./... # all packages in the module
go test -v ./pkg # verbose: prints test names
go test -run TestSum # only tests matching the regext.Error vs t.Fatal
These are the two basic verbs to signal a failure:
| Method | Effect |
|---|---|
t.Log(args...) | Logs but does not fail (visible only with -v or on failure). |
t.Error(args...) | Logs, marks FAIL, continues the test function. |
t.Errorf(fmt, ...) | Like Error with formatting. |
t.Fatal(args...) | Logs, marks FAIL, stops the test (calls runtime.Goexit). |
t.Fatalf(fmt, ...) | Like Fatal with formatting. |
Rule of thumb: use Fatal when continuing makes no sense (e.g. failed setup, imminent nil pointer dereference); otherwise Error, which lets you see all failures in a single run.
func TestOpen(t *testing.T) {
f, err := os.Open("fixtures/sample.txt")
if err != nil {
t.Fatal(err) // if it doesn't open, the next steps would fail on nil
}
defer f.Close()
// ... assert on content with t.Errorf
}Setup and teardown with t.Cleanup
To release resources at the end of a test (or sub-test), use t.Cleanup instead of defer:
func TestDB(t *testing.T) {
db := newTestDB(t)
t.Cleanup(func() { db.Close() })
// ... test
}The advantage over defer: the helper that creates db can register the cleanup internally, so callers don't need to remember it.
Test helpers
The t.Helper() method marks a function as a "helper": when a test fails, the stack trace skips the helper function and points directly to the caller:
func mustParseInt(t *testing.T, s string) int {
t.Helper()
n, err := strconv.Atoi(s)
if err != nil {
t.Fatalf("parse %q: %v", s, err)
}
return n
}Exercises
Define the TestSum function that verifies Sum(2,3) == 5 using t.Errorf with a formatted message.
Solution available after 3 attempts
In TestDiv, use t.Fatal to immediately stop the test if Div returns an unexpected value.
Solution available after 3 attempts
What is the fundamental difference between t.Error and t.Fatal?
if err != nil {
t.???(err)
}