🔨

runn で Web API のリグレッションテストをする

に公開

この記事は PREVENT アドベントカレンダー9日目の記事です。
https://adventar.org/calendars/12152

はじめに

ウェブAPIの動作を確認する目的で、シナリオテストツールである runn を導入しました。
その構成を紹介いたします。

runn とは

シナリオテストツールです。YAML で記述します。
HTTP を叩いたり DB へ接続して「期待する動作 & 状態であるか」をチェックできます。

https://github.com/k1LoW/runn

作者さんの記事が詳しいです。詳細はそちらに譲ります。
https://zenn.dev/k1low/books/runn-cookbook/viewer/about

インストールは 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