はじめに
Table Driven Tests(テーブルドリブンテスト。以下、TDT)とは、入力値と期待値などをもったテストケースを用意し、順次実行していくテスト手法です。
ここでいうTableとはエクセルのようなスプレッドシート、すなわち表のことを指し、テストケースを網羅した表を用意するようにテストコードを用意していきます。
公式でも使われています。
https://github.com/golang/go/wiki/TableDrivenTests
TDTの例
例として、挨拶メッセージを返すメソッドをテストしてみましょう。
引数の時刻によって「おはよう」「こんにちは」「こんばんは」の3種類のメッセージのうちどれか1種類が返されます。
条件と期待結果は以下のとおりです。
Input | Output |
---|---|
05:00-11:59 | おはよう |
12:00-17:59 | こんにちは |
18:00-04:59 | こんばんは |
メソッドは以下のとおり。
// greeter.go
package main
import "time"
func Greet(time time.Time) string {
hour := time.Hour()
switch {
case 5 <= hour && hour <= 11:
return "おはよう"
case 12 <= hour && hour <= 17:
return "こんにちは"
case (18 <= hour && hour <= 23) || (0 <= hour && hour <= 4):
return "こんばんは"
}
panic("Unknown Hour")
}
ボイラープレートの生成をcweill/gotestsに任せる
公式例のようにテストを書いていこう!としても、なかなか面倒です。
そこでcweill/gotestsを活用していきます。
gotestsはメソッドシグネチャを読み取り、テスト用のボイラープレートを自動生成してくれます(大感謝)。
// 2019/10/08 最新版
go get -u github.com/cweill/gotests@v1.5.3
// greeter.goのテストコードを生成
gotests -w -all greeter.go
テストケースを追加していく
以下のようなgreeter_test.go
が生成されたはずです。
nameがテスト名、argsがGreetの引数、wantが期待結果を指します。
// greeter_test.go
package main
import (
"testing"
"time"
)
func TestGreet(t *testing.T) {
type args struct {
time time.Time
}
tests := []struct {
name string
args args
want string
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Greet(tt.args.time); got != tt.want {
t.Errorf("Greet() = %v, want %v", got, tt.want)
}
})
}
}
実際に// TODO: Add test cases.
の箇所にケースを追加していきましょう。
境界値を意識すると、テストはこんな感じになるかと思います(便宜的に3ケースだけ掲載)。
Name | Input | Output |
---|---|---|
05:00 expects おはよう | 05:00 | おはよう |
11:59 expects おはよう | 11:59 | おはよう |
12:00 expects こんにちは | 12:00 | こんにちは |
コードで網羅すると、テストメソッドは以下のようになります。
package main
import (
"testing"
"time"
)
func TestGreet(t *testing.T) {
type args struct {
time time.Time
}
tests := []struct {
name string
args args
want string
}{
// おはよう
{
name: "05:00 expects おはよう",
args: args{
time: time.Date(2019, 1, 1, 5, 0, 0, 0, time.Local),
},
want: "おはよう",
},
{
name: "11:59 expects おはよう",
args: args{
time: time.Date(2019, 1, 1, 11, 59, 59, 59, time.Local),
},
want: "おはよう",
},
// こんにちは
{
name: "12:00 expects こんにちは",
args: args{
time: time.Date(2019, 1, 1, 12, 0, 0, 0, time.Local),
},
want: "こんにちは",
},
{
name: "17:59 expects こんにちは",
args: args{
time: time.Date(2019, 1, 1, 17, 59, 59, 59, time.Local),
},
want: "こんにちは",
},
// こんばんは
{
name: "18:00 expects こんばんは",
args: args{
time: time.Date(2019, 1, 1, 18, 0, 0, 0, time.Local),
},
want: "こんばんは",
},
{
name: "23:59 expects こんばんは",
args: args{
time: time.Date(2019, 1, 1, 23, 59, 59, 59, time.Local),
},
want: "こんばんは",
},
{
name: "00:00 expects こんばんは",
args: args{
time: time.Date(2019, 1, 1, 0, 0, 0, 0, time.Local),
},
want: "こんばんは",
},
{
name: "04:59 expects こんばんは",
args: args{
time: time.Date(2019, 1, 1, 4, 59, 59, 59, time.Local),
},
want: "こんばんは",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Greet(tt.args.time); got != tt.want {
t.Errorf("Greet() = %v, want %v", got, tt.want)
}
})
}
}
Run
書き終わったら、あとはテストを走らせるだけ。
input/outputが網羅されて、わかりやすいテストになりました。
Running tool: /usr/local/go/bin/go test -timeout 30s github.com/mshrwtnb/sandbox -run ^(TestGreet)$ -v
=== RUN TestGreet
=== RUN TestGreet/05:00_expects_おはよう
=== RUN TestGreet/11:59_expects_おはよう
=== RUN TestGreet/12:00_expects_こんにちは
=== RUN TestGreet/17:59_expects_こんにちは
=== RUN TestGreet/18:00_expects_こんばんは
=== RUN TestGreet/23:59_expects_こんばんは
=== RUN TestGreet/00:00_expects_こんばんは
=== RUN TestGreet/04:59_expects_こんばんは
--- PASS: TestGreet (0.00s)
--- PASS: TestGreet/05:00_expects_おはよう (0.00s)
--- PASS: TestGreet/11:59_expects_おはよう (0.00s)
--- PASS: TestGreet/12:00_expects_こんにちは (0.00s)
--- PASS: TestGreet/17:59_expects_こんにちは (0.00s)
--- PASS: TestGreet/18:00_expects_こんばんは (0.00s)
--- PASS: TestGreet/23:59_expects_こんばんは (0.00s)
--- PASS: TestGreet/00:00_expects_こんばんは (0.00s)
--- PASS: TestGreet/04:59_expects_こんばんは (0.00s)
PASS
ok github.com/mshrwtnb/sandbox 1.309s
Success: Tests passed.
以上