A common mistake is writing one Go test function for every input case. That repeats the same call, expected-value setup, comparison, and error handling, making later changes harder to maintain. A table-driven test stores cases as data and applies shared logic through subtests. section 11.4
Build the test around three elements:
- Define named cases containing inputs and expected results. section 11.4
- Loop over the cases and create one subtest per case with t.Run. section 11.4
- Run an individual case with the parent test name and subtest name through the -run flag. section 11.4
Testing newline removal
For removeNewLineSuffixes, one table can cover empty input, a trailing newline, a trailing carriage-return/newline pair, multiple newlines, and no newline. The loop invokes the function and compares each result with its case's expected value. section 11.4
func TestRemoveNewLineSuffix(t *testing.T) {
tests := map[string]struct { #1
input string
expected string
}{
`empty`: { #2
input: "",
expected: "",
},
`ending with \r\n`: {
input: "a\r\n",
expected: "a",
},
`ending with \n`: {
input: "a\n",
expected: "a",
},
`ending with multiple \n`: {
input: "a\n\n\n",
expected: "a",
},
`ending without newline`: {
input: "a",
expected: "a",
},
}
for name, tt := range tests { #3
t.Run(name, func(t *testing.T) { #4
got := removeNewLineSuffixes(tt.input)
if got != tt.expected {
t.Errorf("got: %s, expected: %s", got, tt.expected)
}
})
}
}
This listing shows the complete table-driven shape: named cases, shared input and expected fields, iteration, and subtest creation. section 11.4





