はじめに
こんにちは、STORESの高田です
STORES には Go 言語で書かれたプロダクトがいくつかあります。今回は、その中で使用しているテストヘルパー関数である testutils.Assert
と応用についてご紹介します
ベースは google/go-cmp
現在のコードベースでは、テスト中でデータの比較を行うのに github.com/google/go-cmp/cmp
を使っています。google/go-cmp の使用例としては以下のようになります
if diff := cmp.Diff(want, got, opts...); diff != "" { t.Errorf("diff (-want +got):\n%s", diff) }
次に testutils.Assert の本体ですが、非常に単純です。以下のようになっていて、
package testutils func Assert(t *testing.T, want, got any, opts ...cmp.Option) { t.Helper() t.Run("Assert", func(t *testing.T) { t.Helper() if diff := cmp.Diff(want, got, opts...); diff != "" { t.Errorf("diff (-want +got):\n%s", diff) } }) }
使い方は次のようになっています
t.Run("...", func(t *testing.T) { ... opt := cmpopts.IgnoreFields(dbEmployee{}, "EntityID", "CreatedAt", "UpdatedAt") testutils.Assert(t, wantDBEmployee, gotEmployee, opt) }
なぜヘルパー関数を作ったのか
これくらいなら共通化までしなくても良いのではないかと思われますが、実際のテストコード中では、下に挙げた例のように want/got の順序が逆になっていたり、t.Errorf()
のメッセージが少し違ったりといったバリエーションが発生し、 go-cmp を使ったコードに一貫性を持たせるのは現実的に難しいように思いました
複数人でメンテナンスをしていると、この粒度まで厳密に揃えるのは現実的には難しいためヘルパー関数にしてしまっています
if diff := cmp.Diff(got, tt.want); diff != "" { t.Errorf("diff -got +want):=n%s", diff) // typo! } if diff := cmp.Diff(want, got, opts...); diff != "" { t.Errorf("diff (-want +got):\n%s", diff) } if diff := cmp.Diff(want, got, opts...); diff != "" { t.Errorf("diff (-want +got)\n%s", diff) }
応用
単純な assert 以外に、テストケース内には特に「テーブルが期待した通り更新されているかどうか」のテストが多数ありました。テストの本筋から離れたエラー処理(SELECT に関するエラー処理など)も含めると、テストしたい内容に対して冗長になっていたため、 testutils.AssertTable
ヘルパー関数を準備しました
generics を使って以下のようなものを準備すると、
package testutils func AssertTable[T any](t *testing.T, db domain.DBConnector, name string, want []T, opts ...cmp.Option) { t.Helper() t.Run("AssertTable_"+name, func(t *testing.T) { t.Helper() query := fmt.Sprintf("SELECT %s FROM %s ORDER BY id ASC", columnsAs(*new(T), "", ""), name) var got []T if err := db.DB().Select(&got, query); err != nil { t.Error(err) return } if diff := cmp.Diff(want, got, opts...); diff != "" { t.Errorf("diff (-want +got):\n%s", diff) } }) }
次のように使用できます
t.Run("...", func(t *testing.T) { ... testutils.AssertTable(t, db, "foo_tokens", []postgres.FooToken{ { RefreshToken: "success-refresh-token-stores", AccessToken: "new-access-token-success-refresh-token-stores", }, }, cmp.Options{ cmpopts.IgnoreFields(postgres.FooToken{}, "ID", "AccessTokenExpiresAt"), }) }
いくつかのテーブルに対してテストを行うことを考えると、なかなか便利なヘルパー関数になっているように思います
ヘルパー関数内の処理は、プロダクトの実情に合わせて使いやすいものを準備できると、テストコードに一貫性を持たせられて見通しも良くなると思いますので、事例として参考にしてもらえればと思います