📌

Table Driven Tests

2023/05/30に公開2

Table Driven Tests

参考:Go言語|プログラミングエッセンス

前回の記事の続きとなります。

GoではTable Driven Testsを推奨しています。難しそうな名前が付いていますが、これは単純にテストケースをテーブルとしてまとめたものです。

sample.go
package hsd

func StringDistance(lhs,rhs string) int {
	return Ditance([]rune(lhs),[]rune(rhs))
}

func Ditance(a,b []rune) int {
	dist := 0
	if len(a) != len(b) {
		return -1
	}

	for i := range a {
		if a[i] != b[i] {
			dist++
		}
	}
	return dist
}
sample_test.go
package hsd

import (
	"reflect"
	"testing"
)

func TestStringDistance(t *testing.T) {
	tests := []struct {
		name string
		lhs  string
		rhs  string
		want int
	}{
		{name: "lhs is longer than rhs", lhs: "foo", rhs: "fo", want: -1},
		{name: "lhs is shorter than rhs", lhs: "fo", rhs: "foo", want: -1},
		{name: "No diff", lhs: "foo", rhs: "foo", want: 0},
		{name: "1 diff", lhs: "foo", rhs: "foh", want: 1},
		{name: "2 diffs", lhs: "foo", rhs: "fhh", want: 2},
		{name: "3 diffs", lhs: "foo", rhs: "hhh", want: 3},
		{name: "multibyte", lhs: "あいう", rhs: "あいえ", want: 1},
	}

	for _, tc := range tests {
		got := StringDistance(tc.lhs,tc.rhs)
		if !reflect.DeepEqual(tc.want,got) {
			t.Fatalf("%s: expected: %v,got: %v", tc.name, tc.want, got)
		}
	}
}

↓実行結果

terminal
go-test % go test
PASS
ok      example.com     0.469s

このように、テーブルで入力条件と期待値をまとめておくことでテストの抜けを見つけやすくなり、テストを追加するのも簡単になります。また網羅性もよくなります。

テーブルに持たせる項目はおおよそ以下になります。

  • テストの名前(name)
  • 入力(この場合はlhsやrhs)
  • 期待値(want)

特にテスト名(name)は重要です。テーブルが多くなると、どのテストで失敗したかわからなくなります。

もちろん、すべてのコードがTable Driven Testsでテストできるわけではありません。境界値チェックやバリエーションチェックが必要な関数に向いています。

このようにテストをしっかりと書いておくことで、以降リファクタリングを行った際に、その変更が間違ったものであることにいち早く気づくことができます。このことが「安全に壊す」なのです。なおリファクタリングする前には必ず、git commitしておきましょう。

Discussion