Description
Table tests are great for reducing repetition, but as the SUT becomes more complex, they become harder to read, maintain, and debug.
Making tests harder to read and maintain at scale negatively impacts velocity, productivity, and overall sentiment on testing.
One common pattern I notice is with logical branches based on variables like shouldError
, shouldCallExecutor
, shouldDoXYZ
. This creates an extremely difficult-to-read set of tests. As an exmple, consider the following test:
tests := []struct{
give string
wantHost string
wantPort string
wantErr error
shouldCallXYZ bool
giveXYZResponse string
}{
// ...
}
for _, tt := range tests {
t.Run( ... ) {
err := DoThing(tt.stuff)
if tt.wantErr != nil {
require.EqualError(t, err, tt.wantErr)
}
require.NoError(t, err)
if tt.shouldCallXYZ{
xyzMock.EXPECT().Call(...).Return(tt.giveXYZResponse, nil)
}
})
}
These tests are confusing. It's hard to tell what's going to be checked in which conditions, and unclear when certain fields of the tests
struct are even used. The reader has to reference logic within the tests
structs, as well as the complex behavioral logic within the test loop to understand what's happening.
Consider again that I need to add another subsystem to the service that has a mock which is called only some of the time. This adds another layer of complexity to the table test, and potentially obstructs all previously written parts of the table.
Having the guidance push to use Table Tests for "repeated patterns" actually can negatively impact code quality. Rather than recommending Table Tests for such patterns, we should recommend using Table tests for situations when ONLY the inputs and outputs of the system differ. It would also be good to recommend to AVOID Table Tests with logic existing inside the test loop.
Aside:
Somewhat unrelated to style, but it's also harder to maintain tests at scale with the dynamic assignment of subtest names. Splitting up table tests more granularly into separate TestXXX
functions makes it easier to keep track of the speed and reliability of tests.