Goのテーブル駆動テストは、記述を工夫することで一覧性を高めることができます。
strings.SplitN関数のテストを例に説明します。
通常の実装
シンプルなので読みにくくはないですが、行数の割に情報量が少なく全体を把握するにはスクロールが必要です。
func TestSplitN_A(t *testing.T) {
type args struct {
s string
sep string
n int
}
tests := []struct {
name string
args args
want []string
}{
{
name: "-1",
args: args{
"A,B,C,D",
",",
-1,
},
want: []string{"A", "B", "C", "D"},
},
{
name: "0",
args: args{
"A,B,C,D",
",",
0,
},
want: nil,
},
{
name: "1",
args: args{
"A,B,C,D",
",",
1,
},
want: []string{"A,B,C,D"},
},
{
name: "2",
args: args{
"A,B,C,D",
",",
2,
},
want: []string{"A", "B,C,D"},
},
{
name: "3",
args: args{
"A,B,C,D",
",",
3,
},
want: []string{"A", "B", "C,D"},
},
{
name: "4",
args: args{
"A,B,C,D",
",",
4,
},
want: []string{"A", "B", "C", "D"},
},
{
name: "empty_string_0",
args: args{
"",
",",
0,
},
want: nil,
},
{
name: "empty_string_1",
args: args{
"",
",",
1,
},
want: []string{""},
},
{
name: "empty_string_2",
args: args{
"",
",",
2,
},
want: []string{""},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := strings.SplitN(tt.args.s, tt.args.sep, tt.args.n); !reflect.DeepEqual(got, tt.want) {
t.Errorf("strings.SplitN() = %v, want %v", got, tt.want)
}
})
}
}
改善1:改行をなくす
改行をなくして1行1テストケースにしました。
スクロールしなくても全体が把握でき、テストケース毎の違いも見やすくなりました。
func TestSplitN_B(t *testing.T) {
type args struct {
s string
sep string
n int
}
tests := []struct {
name string
args args
want []string
}{
// ↓↓↓ 変更部分ここから ↓↓↓
{name: "-1", args: args{"A,B,C,D", ",", -1}, want: []string{"A", "B", "C", "D"}},
{name: "0", args: args{"A,B,C,D", ",", 0}, want: nil},
{name: "1", args: args{"A,B,C,D", ",", 1}, want: []string{"A,B,C,D"}},
{name: "2", args: args{"A,B,C,D", ",", 2}, want: []string{"A", "B,C,D"}},
{name: "3", args: args{"A,B,C,D", ",", 3}, want: []string{"A", "B", "C,D"}},
{name: "4", args: args{"A,B,C,D", ",", 4}, want: []string{"A", "B", "C", "D"}},
{name: "empty_string_0", args: args{"", ",", 0}, want: nil},
{name: "empty_string_1", args: args{"", ",", 1}, want: []string{""}},
{name: "empty_string_2", args: args{"", ",", 2}, want: []string{""}},
// ↑↑↑ 変更部分ここまで ↑↑↑
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := strings.SplitN(tt.args.s, tt.args.sep, tt.args.n); !reflect.DeepEqual(got, tt.want) {
t.Errorf("strings.SplitN() = %v, want %v", got, tt.want)
}
})
}
}
改善2:/* */
コメントを駆使して列を揃える
列を揃えることでテストケース毎の変数の違いがより分かりやすくなるかと思います。
ただ、これは手間が掛かるので、必要に応じて使うのをおすすめします。
func TestSplitN_C(t *testing.T) {
type args struct {
s string
sep string
n int
}
tests := []struct {
name string
args args
want []string
}{
// ↓↓↓ 変更部分ここから ↓↓↓
{name: "-1" /* */, args: args{"A,B,C,D", ",", -1}, want: []string{"A", "B", "C", "D"}},
{name: "0" /* */, args: args{"A,B,C,D", ",", 0}, want: nil},
{name: "1" /* */, args: args{"A,B,C,D", ",", 1}, want: []string{"A,B,C,D"}},
{name: "2" /* */, args: args{"A,B,C,D", ",", 2}, want: []string{"A", "B,C,D"}},
{name: "3" /* */, args: args{"A,B,C,D", ",", 3}, want: []string{"A", "B", "C,D"}},
{name: "4" /* */, args: args{"A,B,C,D", ",", 4}, want: []string{"A", "B", "C", "D"}},
{name: "empty_string_0", args: args{"" /* */, ",", 0}, want: nil},
{name: "empty_string_1", args: args{"" /* */, ",", 1}, want: []string{""}},
{name: "empty_string_2", args: args{"" /* */, ",", 2}, want: []string{""}},
// ↑↑↑ 変更部分ここまで ↑↑↑
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := strings.SplitN(tt.args.s, tt.args.sep, tt.args.n); !reflect.DeepEqual(got, tt.want) {
t.Errorf("strings.SplitN() = %v, want %v", got, tt.want)
}
})
}
}
改善3: ヘルパー関数を用意する
今回のケースではあまり有用ではないですが、ヘルパー関数を使うことで冗長な記述がなくなり見やすくなります。
func TestSplitN_D(t *testing.T) {
type args struct {
s string
sep string
n int
}
// ↓↓↓ 変更部分ここから ↓↓↓
type test struct {
name string
args args
want []string
}
makeTest := func(name string, args args, want []string) test {
return test{name: name, args: args, want: want}
}
tests := []test{
makeTest("-1" /* */, args{"A,B,C,D", ",", -1}, []string{"A", "B", "C", "D"}),
makeTest("0" /* */, args{"A,B,C,D", ",", 0}, nil),
makeTest("1" /* */, args{"A,B,C,D", ",", 1}, []string{"A,B,C,D"}),
makeTest("2" /* */, args{"A,B,C,D", ",", 2}, []string{"A", "B,C,D"}),
makeTest("3" /* */, args{"A,B,C,D", ",", 3}, []string{"A", "B", "C,D"}),
makeTest("4" /* */, args{"A,B,C,D", ",", 4}, []string{"A", "B", "C", "D"}),
makeTest("empty_string_0", args{"" /* */, ",", 0}, nil),
makeTest("empty_string_1", args{"" /* */, ",", 1}, []string{""}),
makeTest("empty_string_2", args{"" /* */, ",", 2}, []string{""}),
}
// ↑↑↑ 変更部分ここまで ↑↑↑
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := strings.SplitN(tt.args.s, tt.args.sep, tt.args.n); !reflect.DeepEqual(got, tt.want) {
t.Errorf("strings.SplitN() = %v, want %v", got, tt.want)
}
})
}
}
まとめ
テストコードは分量が多くなるので読む量も増えますが、こういったちょっとした工夫で読みやすくなり、間違いや漏れなどにも気づきやすくなるかと思います。
今回例に上げたソースコードはこちらに置いてます。
https://github.com/fujiwaram/splitn-test