Closed9
Go + gRPCのシナリオテストにrunnを使用したいメモ
個人的に求めているもの
- gRPCのシナリオテストを実行したい
- 単体テスト、インテグレーションテストレベルであればGoのテストコードとして実行がいいけど、複数のgRPCサービスを組み合わせた時のテストをテストコードで書くのは辛い気がする
- なるべくシンプルにシナリオが書ける方がいい
- CIにのせることも考えたいのでCLIで実行できるようにしたい
候補
- runn シナリオをyamlファイルで書ける。CLIでもGoのテストコードとしても実行できる。
- scenarigo
- postmanいつのまにか正式リリースになっていた。JavaScriptでスクリプトを実行できる。ドキュメントはあるけど日本語の情報や実際のシナリオ例などが少なすぎる。
- datadog DatadogでもgRPCのe2eテストできるみたいなのを見かけた気がしたので。調べたところヘルスチェックとブラウザテストが使えそうだけど求めているものとは違う。
他にもありそうだけどこの中だとrunnかscenarigoが良さそうだけどrunnの方がドキュメントが充実していそう(zenn本公開している。有料だけど8割くらい無料公開されている。READMEもかなり書いてある。)なのでrunnを触ってみる。
install
brewとかでもいけるようだけど今回はgo install。
go install github.com/k1LoW/runn/cmd/runn@latest
runn --version
> runn version 0.67.0
grpcurlからランブックを作成
runn new -- grpcurl --d '{"name": "World"}' localhost:8080 hello.GreetingService.Hello
desc: Generated by `runn new`
runners:
greq: grpc://localhost:8080
steps:
- greq:
hello.GreetingService.Hello:
message:
name: World
ランブックを作成して実行し、テストまで作成する
runn new --and-run --grpc-no-tls -- grpcurl -d '{"name": "World"}' localhost:8080
hello.GreetingService/Hello
desc: Generated by `runn new`
runners:
greq: grpc://localhost:8080
steps:
- greq:
hello.GreetingService/Hello:
message:
name: World
test: |
current.res.headers['content-type'][0] == "application/grpc"
&& compare(current.res.message, {"message":"Hello, World!"})
&& current.res.status == 0
ファイルの作成、ステップの追加
runn new --and-run --grpc-no-tls --out ./runbooks/runbook.yml -- grpcurl -d '{"name": "World"}' localhost:8080 hello.GreetingService/Hello
--out
オプションで出力ファイルを指定。なければ作成するし既に存在していればステップを追記する。
テストコードとして実行
"github.com/k1LoW/runn"
パッケージをインストールしてテストコードとして実行できる。
func TestHello(t *testing.T) {
addr := "127.0.0.1:8080"
l, err := net.Listen("tcp", addr)
if err != nil {
t.Fatal(err)
}
s := grpc.NewServer()
hellopb.RegisterGreetingServiceServer(s, service.NewHelloService())
reflection.Register(s)
go func() {
s.Serve(l)
}()
t.Cleanup(func() {
s.GracefulStop()
})
ctx := context.Background()
opts := []runn.Option{
runn.T(t),
runn.Runner("greq", fmt.Sprintf("grpc://%s", addr)),
}
o, err := runn.Load("runbooks/*.yml", opts...)
if err != nil {
t.Fatal(err)
}
if err := o.RunN(ctx); err != nil {
t.Fatal(err)
}
}
複数ステップの実行
desc: ログインしてHello Worldする
runners:
# gRPCランナーを指定
greq: grpc://localhost:8080
vars:
# username: ${USERNAME}
# password: ${PASSWORD}
username: yamanaka
password: pass
steps:
-
desc: Step.0 ログインする
greq:
hello.GreetingService/Login:
message:
name: "{{ vars.username }}"
pass: "{{ vars.password }}"
test: |
current.res.headers['content-type'][0] == "application/grpc"
&& compare(current.res.message, {"id":"U_0001", "name":"yamanaka"})
&& current.res.status == 0
-
desc: Step.1 ログインしたユーザー名でハローする
greq:
hello.GreetingService/Hello:
message:
name: "{{ previous.res.message.name }}"
test: |
current.res.headers['content-type'][0] == "application/grpc"
&& compare(current.res.message, {"message":"Hello, yamanaka!"})
&& current.res.status == 0
varsの変数は環境変数か?
CLIでシナリオ実行する場合、DBはテスト用で用意しないといけないのか。
実際にシナリオテスト導入するときの動かし方について
- CLIでシナリオ実行 + テスト用の環境必要(アプリとDB)
- テストコードでシナリオ実行 + テスト用の環境必要(DBのみ)
- テストコードでシナリオ実行 + コンテナ(CI実行環境でDBコンテナ立てる) + テストデータはコンテナ起動時に投入するとか -> 遅そう
- テストコードでシナリオ実行 + コンテナ(CI実行環境でDBコンテナ立てる) + テストデータは都度作成&削除して真っ新な状態でテストを実行することに努める -> テストデータ作るのが大変そう、てか大変だと思う
単体テストや結合テストはちゃんと書けている前提なら3, 4までやるとテストコードのコード量爆発しそうだし大変だと思うので1か2でやれると楽なんじゃないだろうか
QAチームのようにシナリオテストを作成・実行するチームと開発チームが分かれているならなおさら1, 2の方がやりやすそう。一回テストデータ作ったら使い回しもできて楽そうだし。
デメリットとしてテストデータを都度作成せずに更新していくような運用だと、予期せぬデータ更新でテスト壊れるとかがあるはず。まあ、そしたらなおせばいいのだけど
完全にまっさらでやりたい派は3とか4の方がいいのかな。個人的にはDBまっさらで毎回テスト実行したいけどシナリオテストとかになると大変そうすぎてできるなら1,2がいいなと思った。
CLIでもテストコードでやるにしてもどっちにしろ使い心地は申し分なくてもしシナリオテストやろうみたいになったら採用したい
このスクラップは2023/04/07にクローズされました