runn で Web API のリグレッションテストをする
この記事は PREVENT アドベントカレンダー9日目の記事です。
はじめに
ウェブAPIの動作を確認する目的で、シナリオテストツールである runn を導入しました。
その構成を紹介いたします。
runn とは
シナリオテストツールです。YAML で記述します。
HTTP を叩いたり DB へ接続して「期待する動作 & 状態であるか」をチェックできます。
作者さんの記事が詳しいです。詳細はそちらに譲ります。
インストールは HomeBrew などでできます。
brew install k1LoW/tap/runn
それではさっそくテストしていきましょう。
1. サンプルアプリ
まずはテスト対象のアプリケーションです。
GET /tasks というエンドポイントを用意しました。タスク情報を返します。
package main
import "github.com/gofiber/fiber/v2"
func main() {
app := fiber.New()
app.Get("/tasks", func(c *fiber.Ctx) error {
tasks := []struct {
Title string `json:"title"`
}{
{Title: "買い物に行く"},
{Title: "ジョギングをする"},
}
return c.JSON(tasks)
})
if err := app.Listen(":3000"); err != nil {
panic(err)
}
}
立ち上げましょう。
$ go run .
┌───────────────────────────────────────────────────┐
│ Fiber v2.52.10 │
│ http://127.0.0.1:3000 │
│ (bound on host 0.0.0.0 and port 3000) │
│ │
│ Handlers ............. 2 Processes ........... 1 │
│ Prefork ....... Disabled PID ............. 79638 │
└───────────────────────────────────────────────────┘
2. テスト
では GET /tasks をテストしましょう。
Runbook
まずテストファイルです。レスポンスをアサーションして、リグレッションテストをします。
# tasks.yml
desc: GET /tasks のリグレッションテスト
runners:
req: http://localhost:3000
steps:
- desc: GET /tasks
req:
/tasks:
get:
headers:
test: |
current.res.body[0].title == "買い物に行く"
&& current.res.body[1].title == "ジョギングをする"
テストを実行
runn を実行しましょう。
$ runn run tasks.yml --verbose
=== GET /tasks のリグレッションテスト (tasks.yml)
--- GET /tasks (0) ... ok
1 scenario, 0 skipped, 0 failures
テストが通りました。
次にレスポンスを変えて再度実行してみます。
$ runn run tasks.yml
F
1) tasks.yml b14fb4938980c993c925fd44f6259cc5d0839121
Failure/Error: test failed on "GET /tasks のリグレッションテスト".steps[0] "GET /tasks": condition is not true
Condition:
current.res.body[0].title == "買い物に行く"
&& current.res.body[1].title == "ジョギングをする"
│
├── current.res.body[0].title == "買い物に行く" => true
│ ├── current.res.body[0].title => "買い物に行く"
│ └── "買い物に行く"
└── current.res.body[1].title == "ジョギングをする" => false
├── current.res.body[1].title => "掃除機をかける"
└── "ジョギングをする"
Failure step (tasks.yml):
7 - desc: GET /tasks
8 req:
9 /tasks:
10 get:
11 headers:
12 test: |
13 current.res.body[0].title == "買い物に行く"
14 && current.res.body[1].title == "ジョギングをする"
1 scenario, 0 skipped, 1 failure
fail しました。無事にテストできてそうです。
以上にてテストは完了です。
これでちょっとした変更をする際に動作が変わってないことを保証でき、安心して修正できますね。
Tips: スナップショットテストにする
上述の Runbook ではアサーションを手続き的に記述しておりますが、、
テストの実行時にスナップショットをとり、次回以降それと比較するようにもできます。
desc: GET /tasks のリグレッションテスト
runners:
req: http://localhost:3000
steps:
- desc: GET /tasks
req:
/tasks:
get:
headers:
bind:
res: current.res
- if: file('tasks.snapshot.json') == nil
desc: スナップショットを作成
dump:
expr: res.body
out: tasks.snapshot.json
- desc: スナップショットと比較
test: compare(res.body, fromJSON(file('tasks.snapshot.json')))
これを実行しましょう。
$ runn run tasks.yml --verbose
=== GET /tasks のリグレッションテスト (tasks.yml)
--- GET /tasks (0) ... ok
--- スナップショットを作成 (1) ... ok
--- スナップショットと比較 (2) ... ok
1 scenario, 0 skipped, 0 failures
初回はスナップショットが存在しません。
そのため tasks.snapshot.json というファイルが作成されます。
$ tree
.
├── tasks.snapshot.json
└── tasks.yml
2回目以降の実行では、tasks.snapshot.json と比較してくれます。
$ runn run tasks.yml --verbose
=== GET /tasks のリグレッションテスト (tasks.yml)
--- GET /tasks (0) ... ok
--- スナップショットを作成 (1) ... skip
--- スナップショットと比較 (2) ... ok
1 scenario, 0 skipped, 0 failures
これによりテストファイルを作成する手間が少し省けました。
場合によりけりですが、おすすめです。
おわり
さくっと動作をチェックしたい時に runn は便利です。カスタムランナーにすれば、ロジックをまとめることもできるので、工夫して改善していけそうですね。
Discussion