@@ -3588,8 +3588,12 @@ See also [go vet: Printf family check](https://kuzminva.wordpress.com/2017/11/07
3588
3588
3589
3589
### Test Tables
3590
3590
3591
- Use table-driven tests with [ subtests] ( https://blog.golang.org/subtests ) to avoid duplicating code when the core
3592
- test logic is repetitive.
3591
+ Table-driven tests with [ subtests] ( https://blog.golang.org/subtests ) can be a helpful pattern for writing tests
3592
+ to avoid duplicating code when the core test logic is repetitive.
3593
+
3594
+ If a system under test needs to be tested against * multiple conditions* where
3595
+ certain parts of the the inputs and outputs change, a table-driven test should
3596
+ be used to reduce redundancy and improve readability.
3593
3597
3594
3598
<table >
3595
3599
<thead ><tr ><th >Bad</th ><th >Good</th ></tr ></thead >
@@ -3686,6 +3690,136 @@ for _, tt := range tests {
3686
3690
}
3687
3691
```
3688
3692
3693
+ #### Avoid Unnecessary Complexity in Table Tests
3694
+
3695
+ Table tests can be difficult to read and maintain if the subtests contain conditional
3696
+ assertions or other branching logic. Table tests should ** NOT** be used whenever
3697
+ there needs to be complex or conditional logic inside subtests (i.e. complex logic inside the ` for ` loop).
3698
+
3699
+ Large, complex table tests harm readability and maintainability because test readers may
3700
+ have difficulty debugging test failures that occur.
3701
+
3702
+ Table tests like this should be split into either multiple test tables or multiple
3703
+ individual ` Test... ` functions.
3704
+
3705
+ Some ideals to aim for are:
3706
+
3707
+ * Focus on the narrowest unit of behavior
3708
+ * Minimize "test depth", and avoid conditional assertions (see below)
3709
+ * Ensure that all table fields are used in all tests
3710
+ * Ensure that all test logic runs for all table cases
3711
+
3712
+ In this context, "test depth" means "within a given test, the number of
3713
+ successive assertions that require previous assertions to hold" (similar
3714
+ to cyclomatic complexity).
3715
+ Having "shallower" tests means that there are fewer relationships between
3716
+ assertions and, more importantly, that those assertions are less likely
3717
+ to be conditional by default.
3718
+
3719
+ Concretely, table tests can become confusing and difficult to read if they use multiple branching
3720
+ pathways (e.g. ` shouldError ` , ` expectCall ` , etc.), use many ` if ` statements for
3721
+ specific mock expectations (e.g. ` shouldCallFoo ` ), or place functions inside the
3722
+ table (e.g. ` setupMocks func(*FooMock) ` ).
3723
+
3724
+ However, when testing behavior that only
3725
+ changes based on changed input, it may be preferable to group similar cases
3726
+ together in a table test to better illustrate how behavior changes across all inputs,
3727
+ rather than splitting otherwise comparable units into separate tests
3728
+ and making them harder to compare and contrast.
3729
+
3730
+ If the test body is short and straightforward,
3731
+ it's acceptable to have a single branching pathway for success versus failure cases
3732
+ with a table field like ` shouldErr ` to specify error expectations.
3733
+
3734
+ <table >
3735
+ <thead ><tr ><th >Bad</th ><th >Good</th ></tr ></thead >
3736
+ <tbody >
3737
+ <tr ><td >
3738
+
3739
+ ``` go
3740
+ func TestComplicatedTable (t *testing .T ) {
3741
+ tests := []struct {
3742
+ give string
3743
+ want string
3744
+ wantErr error
3745
+ shouldCallX bool
3746
+ shouldCallY bool
3747
+ giveXResponse string
3748
+ giveXErr error
3749
+ giveYResponse string
3750
+ giveYErr error
3751
+ }{
3752
+ // ...
3753
+ }
3754
+
3755
+ for _ , tt := range tests {
3756
+ t.Run (tt.give , func (t *testing.T ) {
3757
+ // setup mocks
3758
+ ctrl := gomock.NewController (t)
3759
+ xMock := xmock.NewMockX (ctrl)
3760
+ if tt.shouldCallX {
3761
+ xMock.EXPECT ().Call ().Return (tt.giveXResponse , tt.giveXErr )
3762
+ }
3763
+ yMock := ymock.NewMockY (ctrl)
3764
+ if tt.shouldCallY {
3765
+ yMock.EXPECT ().Call ().Return (tt.giveYResponse , tt.giveYErr )
3766
+ }
3767
+
3768
+ got , err := DoComplexThing (tt.give , xMock, yMock)
3769
+
3770
+ // verify results
3771
+ if tt.wantErr != nil {
3772
+ require.EqualError (t, err, tt.wantErr )
3773
+ return
3774
+ }
3775
+ require.NoError (t, err)
3776
+ assert.Equal (t, want, got)
3777
+ })
3778
+ }
3779
+ }
3780
+ ```
3781
+
3782
+ </td ><td >
3783
+
3784
+ ``` go
3785
+ func TestShouldCallX (t *testing .T ) {
3786
+ // setup mocks
3787
+ ctrl := gomock.NewController (t)
3788
+ xMock := xmock.NewMockX (ctrl)
3789
+ xMock.EXPECT ().Call ().Return (" XResponse" , nil )
3790
+
3791
+ yMock := ymock.NewMockY (ctrl)
3792
+
3793
+ got , err := DoComplexThing (" inputX" , xMock, yMock)
3794
+
3795
+ require.NoError (t, err)
3796
+ assert.Equal (t, " want" , got)
3797
+ }
3798
+
3799
+ func TestShouldCallYAndFail (t *testing .T ) {
3800
+ // setup mocks
3801
+ ctrl := gomock.NewController (t)
3802
+ xMock := xmock.NewMockX (ctrl)
3803
+
3804
+ yMock := ymock.NewMockY (ctrl)
3805
+ yMock.EXPECT ().Call ().Return (" YResponse" , nil )
3806
+
3807
+ _ , err := DoComplexThing (" inputY" , xMock, yMock)
3808
+ assert.EqualError (t, err, " Y failed" )
3809
+ }
3810
+ ```
3811
+ </td ></tr >
3812
+ </tbody ></table >
3813
+
3814
+ This complexity makes it more difficult to change, understand, and prove the
3815
+ correctness of the test.
3816
+
3817
+ While there are no strict guidelines, readability and maintainability should
3818
+ always be top-of-mind when deciding between Table Tests versus separate tests
3819
+ for multiple inputs/outputs to a system.
3820
+
3821
+ #### Parallel Tests
3822
+
3689
3823
Parallel tests, like some specialized loops (for example, those that spawn
3690
3824
goroutines or capture references as part of the loop body),
3691
3825
must take care to explicitly assign loop variables within the loop's scope to
0 commit comments