17
9

More than 1 year has passed since last update.

お題は不問!Qiita Engineer Festa 2023で記事投稿!

Go言語のテストケースの一覧性を高める

Last updated at Posted at 2023-07-21

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

17
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
9