When several tests call the same function and make the same comparison, keep that logic in one place. A table-driven test stores each input and expected output as a test case, then runs every case through a shared loop. Adding a scenario changes the data, not the assertion code. section 2.3.2
Test several greetings
The named cases cover supported languages, an unsupported language, and an empty input: func TestGreet(t *testing.T) { type testCase struct { lang language want string } tests := map[string]testCase{ "English": {lang: "en", want: "Hello world"}, "French": {lang: "fr", want: "Bonjour le monde"}, "Akkadian, not supported": { lang: "akk", want: `unsupported language: "akk"`, }, "Empty": {lang: "", want: `unsupported language: ""`}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { got := greet(tc.lang) if got != tc.want { t.Errorf("expected: %q, got: %q", tc.want, got) } }) } } Run the test with go test. Each name identifies the scenario whose comparison failed. section 2.3.2 section 2.6
To extend the pattern:
- Define fields for every input and expected result the shared test needs. A case can also include a wantErr Boolean when an error is part of the expected behavior. section 3.1.4
- Include success and failure paths. File-loading cases use an existing JSON file, a missing file, and invalid JSON. section 3.1.4
- Choose a comparison suited to the result. For a slice of structured values, the test can use a comparison helper or reflect.DeepEqual rather than the greeting test’s string comparison. section 3.1.4
- Keep case data convenient to read. A word-game test stores guesses and solutions as strings, converts them to rune slices when calling computeFeedback, and compares the returned feedback. section 5.2.2
The loop expresses the repeatable work: prepare a case, execute the function, and compare the result with its expectation. Give each case a descriptive name, then add edge cases by adding entries to the table. section 2.3.2 section 2.6 section 5.2.2





