gotests を使ってテーブルドリブンテストを簡単に作成する
Golang の推奨されるテスト方法の1つに「テーブルドリブンテスト」というものが存在します。
詳細については公式 wiki にもあるのでそちらをご参照ください。
var flagtests = []struct {
in string
out string
}{
{"%a", "[%a]"},
{"%-a", "[%-a]"},
{"%+a", "[%+a]"},
{"%#a", "[%#a]"},
{"% a", "[% a]"},
{"%0a", "[%0a]"},
{"%1.2a", "[%1.2a]"},
{"%-1.2a", "[%-1.2a]"},
{"%+1.2a", "[%+1.2a]"},
{"%-+1.2a", "[%+-1.2a]"},
{"%-+1.2abc", "[%+-1.2a]bc"},
{"%-1.2abc", "[%-1.2a]bc"},
}
func TestFlagParser(t *testing.T) {
var flagprinter flagPrinter
for _, tt := range flagtests {
t.Run(tt.in, func(t *testing.T) {
s := Sprintf(tt.in, &flagprinter)
if s != tt.out {
t.Errorf("got %q, want %q", s, tt.out)
}
})
}
}
このようにテスト対象となるデータの構造体をスライスにしておいて、それをループ処理にかけることによってテストを実行する方法です。
正常値、異常値、閾値など色んなパターンのテストを漏れがないようにかつ簡潔に書ける方法としておすすめです。
しかし、このテストを毎回自分の手で作成するのはちょっと面倒です。
テストを書くハードルはなるべく下げて、テストをしっかり書くチームにしたいです。
そこで使いたいのが、今回紹介する cli ツールの gotests
です。
gotests
は cli ツールなので、感覚としては go imports
や go vet
のように開発を助けてくれるものになります。
アプリケーションに変な依存関係を持たせるものでは無いのでご安心ください🙌
この記事は、
go version go1.16.3 darwin/arm64
で動作しています。
やる😎
プログラムの題材は何でも良いのですが、とりあえず飯でも尋ねておきましょう。
「朝・昼・晩」どれかを含む文字列を引数に渡すと該当するメニューを返してくれる Meal
関数を作ります。
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(Meal("朝ご飯は何?"))
}
func Meal(s string) string {
switch {
case strings.Contains(s, "朝"):
return "トースト"
case strings.Contains(s, "昼"):
return "そば"
case strings.Contains(s, "晩"):
return "ハンバーグ"
}
return "どれにもマッチしませんでした。"
}
gotests
を入れます。
$ go get -u github.com/cweill/gotests/...
雛形を作成します。
$ gotests -only Meal -w .
package main
import "testing"
func TestMeal(t *testing.T) {
type args struct {
s string
}
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 := Meal(tt.args.s); got != tt.want {
t.Errorf("Meal() = %v, want %v", got, tt.want)
}
})
}
}
-only
オプションを付けて Meal
関数のテストだけを作成するようにしました。オプションは複数あるので公式ドキュメントをご参照ください👨💻
デフォルトでは標準出力に吐き出されますが、多くの場合はテストファイルに書き込んで欲しいと思うので、 -w
オプションも付けています。
中身を見ると、 Meal
関数の引数を認識して args
型を定義してくれています。
テストの名前として付けるための name
と期待値 want
も string 型で定義されています。
非常に分かりやすいですね!!
テストケースを追加すると以下のようになりました。
package main
import "testing"
func TestMeal(t *testing.T) {
type args struct {
s string
}
tests := []struct {
name string
args args
want string
}{
{"トーストが返ること", args{s: "朝"}, "トースト"},
{"そばが返ること", args{s: "昼"}, "そば"},
{"ハンバーグが返ること", args{s: "晩"}, "ハンバーグ"},
{"マッチしないこと", args{s: "hogehoge"}, "どれにもマッチしませんでした。"},
{"トーストが返ること", args{s: "朝ご飯は何?"}, "トースト"},
{"そばが返ること", args{s: "昼ご飯は何?"}, "そば"},
{"ハンバーグが返ること", args{s: "晩ご飯は何?"}, "ハンバーグ"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Meal(tt.args.s); got != tt.want {
t.Errorf("Meal() = %v, want %v", got, tt.want)
}
})
}
}
IN と OUTが明確で後からの追記も簡単なテストができました。
テストケースを追加したい場合には1行追加するだけでOKです。
gotests
便利🧐
テーブルドリブンテストの雛形を簡単に作成することができました。
gotests は積極的に使っていきたいツールになりそうです😊
Discussion