A test case defines an input or situation and the result expected when code runs. An automated test executes that case and reports whether the result matches; looking at output once is not enough. A visually subtle difference, such as an unexpected space character, can otherwise go unnoticed. Repeatable tests help catch changes that break earlier behavior. section 2.1.2 section 2.3.2
Start with one scenario: prepare the input and expected result, call the function, then compare what you got with what you wanted. In a Go Test function, report a mismatch with t.Errorf; run it with go test. Include unsupported or invalid inputs as well as successful ones—those cases check the safety nets. section 2.2.2 section 2.1.2
Listing 2.13 main_internal_test.go: Table-driven tests
func TestGreet(t *testing.T) {
type testCase struct {
lang language
want string
}
var tests = map[string]testCase{ #1
"English": {
lang: "en",
want: "Hello world",
},
"French": {
lang: "fr",
want: "Bonjour le monde",
},
"Akkadian, not supported": {
lang: "akk",
want: `unsupported language: "akk"`,
},
"Greek": {
lang: "el",
want: "Χαίρετε Κόσμε",
},
...
"Empty": {
lang: "",
want: `unsupported language: ""`,
},
}
// range over all the scenarios
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
got := greet(tc.lang) #2
if got != tc.want { #3
t.Errorf("expected: %q, got: %q", tc.want, got)
}
})
}
}
The greeting test stores each scenario’s language and expected message together, then runs named subtests through the same comparison. Adding a case requires another table entry rather than another copy of the test body. section 2.3.2
Choose the shape that keeps each case clear. A table works when scenarios share an execution and assertion pattern. When assertions differ substantially, use separate named functions or named subtests. For values generated during execution, assert the stable property you require—for example, that an ID or creation time is not empty—rather than expecting a fixed value. section 2.3.2 section 10.3.1
Test the edge, not an accidental detail
For a maze solver, tiny image fixtures represent a cross, branches, dead ends, and a path to treasure. Each case checks how many paths the solver publishes. A buffered channel holds those messages while the test runs; the test does not assume an order for neighboring paths that the implementation does not guarantee. section 9.3.6
Keep the test’s scope deliberate. A handler unit test can supply a prepared URL path value and check its response; testing the full request flow would require an integration test. A service handler can receive a small interface and a stub instead of starting an external database. Conversely, if a unit test would merely duplicate an implementation detail, an integration test across operations may be more useful. A passing test establishes the checked behavior, not the absence of every bug. section 11.3.1 section 8.4.3 section 7.1.3 section 7.3




















































