🧪

Goにおけるテストフィクスチャ

2025/02/05に公開

これは元の記事(著者:私)の翻訳です。

テストを行う際、しばしば何らかのテストデータが必要になります。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