Go モックとスタブのジェネレーターが簡単に
ネットワーク関連のテスト(APIやデータベース呼び出し)は本当に面倒
モックデータを作るのに時間を取られすぎて、本来のテストやアサーションに集中できない。
フェイクモックを作ると、不正確なデータや現実味のないデータを使ってしまう可能性がある。
APIの仕様(コントラクト)が変わるたびに、すべて手作業で修正する必要があり、非常に手間がかかる。
効率的にHTTPの依存関係を模倣する方法を探していたところ、GoogleのGitHubリポジトリで興味深いライブラリを見つけました。
github.com/google/go-replayers
このライブラリは、HTTPの依存関係を記録できるため、Googleでも使われているなら期待できそうだと思いました。しかし、実際に使ってみるといくつかの課題がありました。
記録されたスタブを読む・編集するのが大変。特に本番環境のデータを記録する場合、個人情報やキーなどの機密情報をマスクするのが難しい。
スタブを最新の状態に保つのが手作業で、API開発者がモックデータを見直すことはほとんどない。そのため、誤った前提に基づいたテストが生まれるリスクがある。
もっとリアルで分かりやすいモックやスタブを簡単に作成できて、それ自体がAPIテストケースとして使えたら最高じゃない?
そこで、Keployに新しいモック・スタブライブラリを作り、TDDのワークフローを改善しました。このツールの最大の特徴は、実際のAPIやデータベース呼び出しからテストとモックを生成できることです。
従来の gomock のようなライブラリは型を生成するだけですが、Keployなら本物のAPI呼び出しをモックとして再利用できます。
さらに、APIコールのテストをそのままモックやスタブとして使えるし、逆にモックをテストケースとしても利用できる!
Keployを使ったユニットテストの実践
まず、ディレクトリ構成を整えます。
$ go mod init mocking
go: creating new go.mod: module mocking
$ mkdir -p external
$ touch external/{external.go,external_test.go}
$ tree .
.
├── external
│ ├── external.go
│ └── external_test.go
└── go.mod
1 directory, 3 files
サンプルコードは以下のGitHub Gistから取得できます。
example.go & example_test.go
スタブの作成
まず、Keployをインストールして起動します。
Mac
curl --silent --location "https://github.com/keploy/keploy/releases/latest/download/keploy_darwin_all.tar.gz" | tar xz -C /tmp
sudo mv /tmp/keploy /usr/local/bin && keploy
Linux
curl --silent --location "https://github.com/keploy/keploy/releases/latest/download/keploy_linux_amd64.tar.gz" | tar xz -C /tmp
sudo mv /tmp/keploy /usr/local/bin && keploy
Keployサーバーが起動すると、次のようなログが表示されます。
keploy 0.9.1
.2023-06-15T15:30:58.736+0530 INFO server/server.go:217 keploy started at port 6789
KeployでHTTPクライアントをラップ
KeployのGo SDKを使い、HTTPクライアントをKeployの net/http ラッパーでラップします。
go
import "github.com/keploy/go-sdk/integrations/khttpclient"
...
// HTTPクライアントをKeploy SDKでラップ
interceptor := khttpclient.NewInterceptor(http.DefaultTransport)
client := &http.Client{
Transport: interceptor,
}
ext = external.New(server.URL, client, time.Second)
...
テスト実行とスタブの記録
Keploy SDKを開始し、HTTPコールを記録します。
go
ctx := mock.NewContext(mock.Config{
Name: "hello", // 各モック/スタブに一意の名前を付ける(省略すると自動生成)
Mode: keploy.MODE_RECORD, // RECORDモードで記録
})
for i := range tt {
tc := tt[i]
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
gotData, gotErr := ext.FetchData(ctx, tc.id)
if !errors.Is(gotErr, tc.wantErr) {
fatal(t, tc.wantErr, gotErr)
}
if !reflect.DeepEqual(gotData, tc.wantData) {
fatal(t, tc.wantData, gotData)
}
})
}
Keployの context.Context をすべての依存関係に渡す必要があります。このコンテキストを使うことで、アプリ全体のテストを同期できます。
go test -v -count=1 ./...
成功すると、次のようなログが表示されます。
=== RUN TestExternal_FetchData
💡⚡️ Keploy created new mocking context in record mode for hello.
=== RUN TestExternal_FetchData/response_not_ok
=== RUN TestExternal_FetchData/data_found
🟠 Captured the mocked outputs for Http dependency call with meta: map[name:Http operation:GET type:HTTP_CLIENT]
--- PASS: TestExternal_FetchData (0.00s)
--- PASS: TestExternal_FetchData/data_found (0.00s)
--- PASS: TestExternal_FetchData/response_not_ok (0.00s)
PASS
ok mocking/external 0.165s
記録されたスタブは mocks フォルダに保存されます(保存先は変更可能)。
テストモードで実行
次に、Keployのモードを MODE_TEST に切り替えます。
go
ctx := mock.NewContext(mock.Config{
Name: "hello",
Mode: keploy.MODE_TEST, // テストモードに変更
})
再度 go test を実行すると、Keployが記録済みのスタブを使用してテストを実行します。
go test -v -count=1 ./...
=== RUN TestExternal_FetchData
💡⚡️ Keploy created new mocking context in test mode for hello.
🤡 Returned the mocked outputs for Http dependency call with meta: map[name:Http operation:GET type:HTTP_CLIENT]
--- PASS: TestExternal_FetchData (0.00s)
PASS
ok mocking/external 0.165s
やった!スタブの準備が整いました。
デフォルトでは mocks フォルダに作成されますが、簡単にカスタマイズできます。スタブが作成されたら、テストファイル内で定義された Keploy オブジェクトのモードを test に切り替えましょう。
⌄
ctx := mock.NewContext(mock.Config{
Name: "hello", // 各モック/スタブごとに一意の名前が必要です。記録時に指定しない場合は自動生成されますが、テスト時には必須です。
Mode: keploy.MODE_TEST, // MODE_TEST または MODE_OFF を指定可能。デフォルトは MODE_TEST です。
})
これで準備完了です!さっそくテストを再実行してみましょう。
sh
➜ mocking go test -v -count=1 ./...
mocking server
mocking external
run tests
=== RUN TestExternal_FetchData
💡⚡️ Keploy が「hello」という名前でテストモードのモックコンテキストを作成しました。
依存関係のログが表示されない場合、依存関係がラップされていない可能性があります。
=== RUN TestExternal_FetchData/response_not_ok
=== PAUSE TestExternal_FetchData/response_not_ok
=== RUN TestExternal_FetchData/data_found
=== PAUSE TestExternal_FetchData/data_found
=== CONT TestExternal_FetchData/response_not_ok
=== CONT TestExternal_FetchData/data_found
🤡 HTTP 依存関係の呼び出しをモックした出力を返しました。メタ情報: `map[name:Http operation:GET type:HTTP_CLIENT]`
🤡 HTTP 依存関係の呼び出しをモックした出力を返しました。メタ情報: `map[name:Http operation:GET type:HTTP_CLIENT]`
--- PASS: TestExternal_FetchData (0.00s)
--- PASS: TestExternal_FetchData/response_not_ok (0.00s)
--- PASS: TestExternal_FetchData/data_found (0.00s)
PASS
ok mocking/external 0.247s
まるで魔法のようですね!🪄
Keploy は、以前記録したスタブのレスポンスを自動的に提供します!
完全なコードはこちらからご覧いただけます。
Keploy では、Postgres、MySQL、gRPC クライアント/サーバーなど、さまざまな依存関係のリアルなスタブ(サービス仮想化)を作成できます。手順はどれも同じです。ぜひ試してみて、Keploy の Slack チャンネルでフィードバックをお寄せください!
さらに朗報です!🎉
次期バージョンの Keploy(TestGPT になるかも?)では、Generative AI の魔法を使って 本当に動作する テストコードを自動生成します!
GPT ベースのテスト生成ツールによくある "使えない半端なテストコード" に悩まされることは、もうありません!🚀
Discussion