Goにおけるテストフィクスチャ
これは元の記事(著者:私)の翻訳です。
テストを行う際、しばしば何らかのテストデータが必要になります。Goでは、テストデータはしばしば構造体の形式をとります。この記事では、再利用可能なテストデータを作成するためのさまざまな方法を探り、また、現在私が開発している、テストデータ作成を支援するライブラリを紹介します。
テストデータの作成
まず、次のような構造体があると仮定しましょう。
type Book struct{
ID int
Title string
}
type Author struct {
ID int
Name string
Books []Book
}
func GetAuthor() Author {
// return an author
}
ほとんどの一般的なIDEでは、関数の単体テストを生成できます。多くの場合、次のようなテーブル駆動テストが生成されます。
func TestGetAuthor(t *testing.T) {
tests := []struct {
name string
want Author
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := GetAuthor(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetAuthor() = %v, want %v", got, tt.want)
}
})
}
}
ここで、各テストケースでAuthor
を直接初期化することは、シンプルでわかりやすいアプローチです。しかし、これはやや時間がかかり、反復的です。そして、Author
が変更されると、すべてのテストを修正する必要があります。
テスト内で構造体を直接初期化する代わりに、関数パラメータを持つビルダー関数を作成できます。
type Opts func(*Author)
func BuildAuthor(options ...opts) Author {
// we can set some default values here
author := &Author{ID: 1}
for _,option := range options {
option(author)
}
return *Author
}
// usage
someAuthor := BuildAuthor(func(a *Author){ a.Name="Name"})
このオプションを使用すると、再利用可能なテストデータを作成でき、テストケース内でBuildAuthor
を簡単に呼び出すことができます。
私は上記のようなコードを作成することが多かったので、テストデータの作成を支援するためのライブラリを作成しました。
ライブラリの紹介
testcraftの動機は、テストデータ作成に必要なボイラープレートの一部を削除し、いくつかの小さなヘルパー関数を追加することでした。上記の単純な例は、次のようになります。
authorFactory := testcraft.NewFactory(Author{}).Attr(func(a *Author) error {
a.ID = 1
a.Name="Name"
return nil
})
someAuthor,err := authorFactory.Build()
// or use MustBuild, MustBuild panics on error
someAuthor := authorFactory.MustBuild()
これは基本的に、以前に示したBuildAuthor
関数と同じコードです。
しかし、testcraftでは、もっと多くのことができます。いくつかのヘルパーを見てみましょう。常に同じIDを持っているのは、やや非現実的です。IDのシーケンスを取得するには、Sequence
を作成できます。
// NewSequencer can take any of the following types
// int, int32, int64, int8, float32, float64
authorSeq := NewSequencer(1)
// in the previously declared factory, we can now use
a.ID = authorSeq.Next()
Build
が呼び出されるたびに、IDは1ずつ増加します。増分はuserSeq.SetIncrement(2)
で変更できます。
単にいくつかのデータが必要で、値について気にしない場合は、構造体をランダムな値で初期化するRandomize
関数があります。
authorSeq := NewSequencer(1)
authorFactory := testcraft.NewFactory(Author{}).Attr(func(a *Author) error {
a.ID = authorSeq.Next()
a.Name="Name"
return nil
})
// Randomize ignores the previously set values
randomAuthor, err := authorFactory.Randomize()
// RandomizeWithAttrs will apply the previously set attributes and randomize the rest.
randomAuthor2, err := authorFactory.RandomizeWithAttrs()
次に、ファクトリを結合し、Multiple
関数を使用する方法を見てみましょう。
// As a reminder, here are the structs again.
type Book struct{
ID int
Title string
}
type Author struct {
ID int
Name string
Books []Book
}
// create sequence for books
bookSeq := NewSequencer(1)
// define book factory
bookFactory := testcraft.NewFactory(Book{}).Attr(func(b *Book) error{
b.ID = bookSeq.Next()
})
// create sequence for author
authorSeq := NewSequencer(1)
// define author factory
authorFactory := testcraft.NewFactory(Author{}).Attr(func(a *Author) error {
a.ID = authorSeq.Next()
// // AlphanumericBetween returns a random string of alphanumeric characters between min(3) and max(10).
a.Name = datagen.AlphanumericBetween(3,10)
// Multiple creates a slice of Book with 5 elements.
a.Books = testcraft.Multiple(5, func(i int) Book {
return bookFactory.MustRandomizeWithAttrs()
// functions prefixed with Must panic on error
})
return nil
})
上記のコードでは、3〜10文字の英数字文字列を生成する際に、datagen
パッケージも初めて垣間見ることができます。
datagen
パッケージはまだ改善の余地があると思いますが、基本的な作業には十分です。
私はtestcraftを積極的に開発しており、さらにいくつかの機能を追加し、データ生成パッケージを改善する予定です。
Discussion