Open36

読者コミュニティ|runn クックブック

norionorio

ブック購入させていただきました。

テストの書き方について質問がございます。
非同期にDBレコードの値が変わるAPIのテストで使ってみたいと思いインストールしました。

下記のようなステップのテストを記述しています。

・API呼び出し
・DB参照

現状ですとAPI呼び出し後、DB参照がすぐに行われ、ステータスが変わる前の値を検証してしまいテストが通りません。

DB参照前に数秒Sleepさせることは可能なのでしょうか?

Ken’ichiro OyamaKen’ichiro Oyama

こんにちは。購入ありがとうございます。

DB参照前に数秒Sleepさせること

おそらく実施したいことは「ステータスが変わることを検証したい」だと思われます。

2つ紹介します。

例として「APIコール後に users テーブルの status カラムの値が 0 から別の値( 1 とか 2 など)になること」を確認するケースを考えます。

1. 単純にsleepさせる

Execランナーをつかって sleep コマンドを挟みます。

steps:
  - 
    req:
      /path/to/endpoint:
        post:
          # (snip)
  - 
    exec:
      command: sleep 5
  -
    db:
      query: SELECT status FROM users;
    test: current.rows[0].status != 0

2. ステータスが変化するまで確認する

loop: セクションを使用してリトライをします。

steps:
  - 
    req:
      /path/to/endpoint:
        post:
          # (snip)
  -
    db:
      query: SELECT status FROM users;
    loop: 
      interval: 5sec # 失敗したら5秒待つ
      count: 3 # 3回リトライする
      until: current.rows[0].status != 0

リトライについては「ステップのリトライを設定する 」で紹介していますのでご覧ください。

norionorio

ご返信ありがとうございます。
execランナーがまさにやりたいことでした。

ご回答ありがとうございます。

calloc134calloc134

初めまして。かろっくと申します。
昨日からrunnを使わせていただいております。このrunnの使い方について、いくつか質問失礼します。
runn run dummy.ymlを実行した際に、一つのシナリオごとにテストの成功/失敗を表示していますが、ステップごとの成功の可否を表示することはできますでしょうか?
また、ステップが一つ失敗したとき、以降のテストが実行されませんが、以降のテストが実行されるようなオプション等は存在しますか?
よろしくお願いいたします。

Ken’ichiro OyamaKen’ichiro Oyama

質問ありがとうございます!

一つのシナリオごとにテストの成功/失敗を表示していますが、ステップごとの成功の可否を表示することはできますでしょうか?

v0.64.1時点ではありません。シナリオの成功=シナリオ内の全ステップの成功(スキップ除く)なのでステップの成功失敗の表示はありません。

ステップが一つ失敗したとき、以降のテストが実行されませんが、以降のテストが実行されるようなオプション等は存在しますか?

v0.64.1時点ではありません。ステップの失敗が発生すると即シナリオ失敗となっています。


なお、後者の質問に対してですが、現在オプションの作成を検討しておりそのための実装を追加している最中です。
そうすると必然的に前者の質問(ステップごとの失敗)についても対応していくことになるのではないかと思います。

calloc134calloc134

ご返信ありがとうございます!実装お待ちしています。

Ken’ichiro OyamaKen’ichiro Oyama

ステップが一つ失敗したとき、以降のテストが実行されませんが、以降のテストが実行されるようなオプション等は存在しますか?

こちらは v0.66.0 で force: セクションで実現できるようになりましたので共有しておきます。

Ken’ichiro OyamaKen’ichiro Oyama

一つのシナリオごとにテストの成功/失敗を表示していますが、ステップごとの成功の可否を表示することはできますでしょうか?

こちらは v0.68.0 で --verbose オプションを付与して runn run を実行することで確認できるようになりましたので共有します。

harachanharachan

素敵なツールありがとうございます!!
「Homebrewでインストールする」の内容が brewコマンドを用いたものではなく go installしているものだったので誤植かなと思い報告させていただきます
https://zenn.dev/k1low/books/runn-cookbook/viewer/install#homebrewでインストールする

びきニキびきニキ

はじめまして!1つ質問させてください。
Sessionベースの認証が必要なAPIを突破する方法はありますか?見落としていたらすみません🙇
お手隙のタイミングで教えていただけると幸いです!よろしくお願いします。

Ken’ichiro OyamaKen’ichiro Oyama

Sessionベースといっても一概に1つの実装に限定できるわけではありません。

https://www.php.net/manual/ja/book.session.php

例えば、クライアント側にSession IDを渡すことで認証状態を維持しているのであれば、そのSession IDの取得方法とSession IDの受け渡し方法をrunnで模倣できれば可能だと思います。

PHPマニュアルでもSession IDの受け渡し方法として2つ紹介されています。
https://www.php.net/manual/ja/session.idpassing.php

クライアント側にSession IDを受け渡すことができればいいので、実装方法も、上記2つに限らないでしょう。

例えばPHPアプリケーションだと(何も設定を変えていなければ) PHPSESSID という名前のCookieを経由してSession IDをブラウザ側に保存しているはずです。

https://developer.mozilla.org/ja/docs/Web/HTTP/Cookies
https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Set-Cookie

びきニキびきニキ

参考URLまでありがとうございます。仰る通り「クライアント側にSession IDを渡すことで認証状態を維持」という方法で実装しているようなので、runnで模倣する形で実装してみようと思います。ふわふわした内容の質問で失礼しました…!

toganatogana

シナリオベースのテストを試してみたくて runn を利用してみているのですが、自動生成されたファイルで実行エラーとなり原因がわからないので質問させてください。

runn クックブック を参考にランブックを作成して実行しています。
実行時に invalid char escape (10:2306) となってしまうのですが、レスポンスに含まれていてはいけない文字などありますか?

実行した内容としては下記になります。

$ runn -v
runn version 0.80.2
  1. runn new コマンドで --out--and-run オプションを利用して test 付きのランファイルを作成する
$ runn new --and-run --out runbook.yml -- curl http://localhost/api/tasks -H "accept: application/json"
  1. 作成されたファイルを見る
$ cat ./runbook.yml
desc: Generated by `runn new`
runners:
  req: http://localhost
steps:
- req:
    /api/tasks:
      get:
        headers:
          Accept: application/json
        body: null
  test: |
    current.res.status == 200
    && current.res.headers['Access-Control-Allow-Origin'][0] == "*"
    && current.res.headers['Cache-Control'][0] == "no-cache, private"
    && current.res.headers['Content-Type'][0] == "application/json"
    && 'Date' in current.res.headers
    && current.res.headers['Host'][0] == "localhost"
    && current.res.headers['X-Powered-By'][0] == "PHP/8.2.8"
    && current.res.headers['X-Ratelimit-Limit'][0] == "60"
    && current.res.headers['X-Ratelimit-Remaining'][0] == "59"
    && compare(current.res.body, {"data":[{"id":"01h7j21q12erkf1fwna29shhw7","content":"test2","is_completed":true,"created_at":"2023-08-11T10:25:06.082000Z","content_changed_at":"2023-08-11T10:25:06.083000Z","complete_changed_at":"2023-08-11T10:25:06.085000Z"},{"id":"01h7j21q0xwrh65qdwcskh30kn","content":"test4","is_completed":true,"created_at":"2023-08-11T10:25:06.077000Z","content_changed_at":"2023-08-11T10:25:06.078000Z","complete_changed_at":"2023-08-11T10:25:06.081000Z"},{"id":"01h7j21q0r8x1rnqx63gy4mt4w","content":"test2","is_completed":true,"created_at":"2023-08-11T10:25:06.072000Z","content_changed_at":"2023-08-11T10:25:06.073000Z","complete_changed_at":"2023-08-11T10:25:06.076000Z"},{"id":"01h7j21q0kccqz8dyk6y6tmv0q","content":"test4","is_completed":true,"created_at":"2023-08-11T10:25:06.067000Z","content_changed_at":"2023-08-11T10:25:06.068000Z","complete_changed_at":"2023-08-11T10:25:06.071000Z"},{"id":"01h7j21q0fwham459j2y614tp6","content":"test4","is_completed":true,"created_at":"2023-08-11T10:25:06.063000Z","content_changed_at":"2023-08-11T10:25:06.064000Z","complete_changed_at":"2023-08-11T10:25:06.066000Z"},{"id":"01h7j21q0agq9t4kb6zc4w1kff","content":"test1","is_completed":false,"created_at":"2023-08-11T10:25:06.058000Z","content_changed_at":"2023-08-11T10:25:06.060000Z","complete_changed_at":"2023-08-11T10:25:06.061000Z"},{"id":"01h7j21q057934evj87arm9qfn","content":"test4","is_completed":false,"created_at":"2023-08-11T10:25:06.053000Z","content_changed_at":"2023-08-11T10:25:06.055000Z","complete_changed_at":"2023-08-11T10:25:06.056000Z"},{"id":"01h7j21q00atfjtw7dwa0cxdea","content":"test1","is_completed":false,"created_at":"2023-08-11T10:25:06.048000Z","content_changed_at":"2023-08-11T10:25:06.051000Z","complete_changed_at":"2023-08-11T10:25:06.052000Z"},{"id":"01h7j21pzvgdw9njfrcmb3gy2g","content":"test2","is_completed":false,"created_at":"2023-08-11T10:25:06.043000Z","content_changed_at":"2023-08-11T10:25:06.045000Z","complete_changed_at":"2023-08-11T10:25:06.047000Z"},{"id":"01h7j21pzpwccrn0qaekedqwah","content":"test2","is_completed":false,"created_at":"2023-08-11T10:25:06.038000Z","content_changed_at":"2023-08-11T10:25:06.041000Z","complete_changed_at":"2023-08-11T10:25:06.042000Z"}],"links":{"first":null,"last":null,"prev":null,"next":"http:\/\/localhost\/api\/tasks?cursor=eyJpZCI6IjAxaDdqMjFwenB3Y2NybjBxYWVrZWRxd2FoIiwiX3BvaW50c1RvTmV4dEl0ZW1zIjp0cnVlfQ"},"meta":{"path":"http:\/\/localhost\/api\/tasks","per_page":10,"next_cursor":"eyJpZCI6IjAxaDdqMjFwenB3Y2NybjBxYWVrZWRxd2FoIiwiX3BvaW50c1RvTmV4dEl0ZW1zIjp0cnVlfQ","prev_cursor":null}})
  1. 実行する
$ runn run ./runbook.yml
F

1) ./runbook.yml 636eb5202577df2773e016cec9ed364eb920d41c
  Failure/Error: test failed on 'Generated by `runn new`'.steps[0]: invalid char escape (10:2306)
   | && compare(current.res.body, {"data":[{"id":"01h7j21q12erkf1fwna29shhw7","content":"test2","is_completed":true,"created_at":"2023-08-11T10:25:06.082000Z","content_changed_at":"2023-08-11T10:25:06.083000Z","complete_changed_at":"2023-08-11T10:25:06.085000Z"},{"id":"01h7j21q0xwrh65qdwcskh30kn","content":"test4","is_completed":true,"created_at":"2023-08-11T10:25:06.077000Z","content_changed_at":"2023-08-11T10:25:06.078000Z","complete_changed_at":"2023-08-11T10:25:06.081000Z"},{"id":"01h7j21q0r8x1rnqx63gy4mt4w","content":"test2","is_completed":true,"created_at":"2023-08-11T10:25:06.072000Z","content_changed_at":"2023-08-11T10:25:06.073000Z","complete_changed_at":"2023-08-11T10:25:06.076000Z"},{"id":"01h7j21q0kccqz8dyk6y6tmv0q","content":"test4","is_completed":true,"created_at":"2023-08-11T10:25:06.067000Z","content_changed_at":"2023-08-11T10:25:06.068000Z","complete_changed_at":"2023-08-11T10:25:06.071000Z"},{"id":"01h7j21q0fwham459j2y614tp6","content":"test4","is_completed":true,"created_at":"2023-08-11T10:25:06.063000Z","content_changed_at":"2023-08-11T10:25:06.064000Z","complete_changed_at":"2023-08-11T10:25:06.066000Z"},{"id":"01h7j21q0agq9t4kb6zc4w1kff","content":"test1","is_completed":false,"created_at":"2023-08-11T10:25:06.058000Z","content_changed_at":"2023-08-11T10:25:06.060000Z","complete_changed_at":"2023-08-11T10:25:06.061000Z"},{"id":"01h7j21q057934evj87arm9qfn","content":"test4","is_completed":false,"created_at":"2023-08-11T10:25:06.053000Z","content_changed_at":"2023-08-11T10:25:06.055000Z","complete_changed_at":"2023-08-11T10:25:06.056000Z"},{"id":"01h7j21q00atfjtw7dwa0cxdea","content":"test1","is_completed":false,"created_at":"2023-08-11T10:25:06.048000Z","content_changed_at":"2023-08-11T10:25:06.051000Z","complete_changed_at":"2023-08-11T10:25:06.052000Z"},{"id":"01h7j21pzvgdw9njfrcmb3gy2g","content":"test2","is_completed":false,"created_at":"2023-08-11T10:25:06.043000Z","content_changed_at":"2023-08-11T10:25:06.045000Z","complete_changed_at":"2023-08-11T10:25:06.047000Z"},{"id":"01h7j21pzpwccrn0qaekedqwah","content":"test2","is_completed":false,"created_at":"2023-08-11T10:25:06.038000Z","content_changed_at":"2023-08-11T10:25:06.041000Z","complete_changed_at":"2023-08-11T10:25:06.042000Z"}],"links":{"first":null,"last":null,"prev":null,"next":"http:\/\/localhost\/api\/tasks?cursor=eyJpZCI6IjAxaDdqMjFwenB3Y2NybjBxYWVrZWRxd2FoIiwiX3BvaW50c1RvTmV4dEl0ZW1zIjp0cnVlfQ"},"meta":{"path":"http:\/\/localhost\/api\/tasks","per_page":10,"next_cursor":"eyJpZCI6IjAxaDdqMjFwenB3Y2NybjBxYWVrZWRxd2FoIiwiX3BvaW50c1RvTmV4dEl0ZW1zIjp0cnVlfQ","prev_cursor":null}})
   | .................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................^
  Failure step (./runbook.yml):
   5 - req:
   6     /api/tasks:
   7       get:
   8         headers:
   9           Accept: application/json
  10         body: null
  11   test: |
  12     current.res.status == 200
  13     && current.res.headers['Access-Control-Allow-Origin'][0] == "*"
  14     && current.res.headers['Cache-Control'][0] == "no-cache, private"
  15     && current.res.headers['Content-Type'][0] == "application/json"
  16     && 'Date' in current.res.headers
  17     && current.res.headers['Host'][0] == "localhost"
  18     && current.res.headers['X-Powered-By'][0] == "PHP/8.2.8"
  19     && current.res.headers['X-Ratelimit-Limit'][0] == "60"
  20     && current.res.headers['X-Ratelimit-Remaining'][0] == "59"
  21     && compare(current.res.body, {"data":[{"id":"01h7j21q12erkf1fwna29shhw7","content":"test2","is_completed":true,"created_at":"2023-08-11T10:25:06.082000Z","content_changed_at":"2023-08-11T10:25:06.083000Z","complete_changed_at":"2023-08-11T10:25:06.085000Z"},{"id":"01h7j21q0xwrh65qdwcskh30kn","content":"test4","is_completed":true,"created_at":"2023-08-11T10:25:06.077000Z","content_changed_at":"2023-08-11T10:25:06.078000Z","complete_changed_at":"2023-08-11T10:25:06.081000Z"},{"id":"01h7j21q0r8x1rnqx63gy4mt4w","content":"test2","is_completed":true,"created_at":"2023-08-11T10:25:06.072000Z","content_changed_at":"2023-08-11T10:25:06.073000Z","complete_changed_at":"2023-08-11T10:25:06.076000Z"},{"id":"01h7j21q0kccqz8dyk6y6tmv0q","content":"test4","is_completed":true,"created_at":"2023-08-11T10:25:06.067000Z","content_changed_at":"2023-08-11T10:25:06.068000Z","complete_changed_at":"2023-08-11T10:25:06.071000Z"},{"id":"01h7j21q0fwham459j2y614tp6","content":"test4","is_completed":true,"created_at":"2023-08-11T10:25:06.063000Z","content_changed_at":"2023-08-11T10:25:06.064000Z","complete_changed_at":"2023-08-11T10:25:06.066000Z"},{"id":"01h7j21q0agq9t4kb6zc4w1kff","content":"test1","is_completed":false,"created_at":"2023-08-11T10:25:06.058000Z","content_changed_at":"2023-08-11T10:25:06.060000Z","complete_changed_at":"2023-08-11T10:25:06.061000Z"},{"id":"01h7j21q057934evj87arm9qfn","content":"test4","is_completed":false,"created_at":"2023-08-11T10:25:06.053000Z","content_changed_at":"2023-08-11T10:25:06.055000Z","complete_changed_at":"2023-08-11T10:25:06.056000Z"},{"id":"01h7j21q00atfjtw7dwa0cxdea","content":"test1","is_completed":false,"created_at":"2023-08-11T10:25:06.048000Z","content_changed_at":"2023-08-11T10:25:06.051000Z","complete_changed_at":"2023-08-11T10:25:06.052000Z"},{"id":"01h7j21pzvgdw9njfrcmb3gy2g","content":"test2","is_completed":false,"created_at":"2023-08-11T10:25:06.043000Z","content_changed_at":"2023-08-11T10:25:06.045000Z","complete_changed_at":"2023-08-11T10:25:06.047000Z"},{"id":"01h7j21pzpwccrn0qaekedqwah","content":"test2","is_completed":false,"created_at":"2023-08-11T10:25:06.038000Z","content_changed_at":"2023-08-11T10:25:06.041000Z","complete_changed_at":"2023-08-11T10:25:06.042000Z"}],"links":{"first":null,"last":null,"prev":null,"next":"http:\/\/localhost\/api\/tasks?cursor=eyJpZCI6IjAxaDdqMjFwenB3Y2NybjBxYWVrZWRxd2FoIiwiX3BvaW50c1RvTmV4dEl0ZW1zIjp0cnVlfQ"},"meta":{"path":"http:\/\/localhost\/api\/tasks","per_page":10,"next_cursor":"eyJpZCI6IjAxaDdqMjFwenB3Y2NybjBxYWVrZWRxd2FoIiwiX3BvaW50c1RvTmV4dEl0ZW1zIjp0cnVlfQ","prev_cursor":null}})


1 scenario, 0 skipped, 1 failure
Ken’ichiro OyamaKen’ichiro Oyama

http:\/\/localhost\/api\/tasks\/ が原因っぽいです。
runn側でうまく生成orパースの修正で対応ができないか確認します。

toganatogana

確認ありがとうございます。
よろしくお願いします!

toganatogana

ありがとうございます!
実行できるようになりました!

以前と runn new の生成するコードが変わり \// として生成されました。

nittanitta

こんにちは。runnクックブックに載っていたコマンドを実行したところ以下の様なエラーが発生しました。原因がわからないので、教えていただきたいです🙏

$ uname -m
arm64
$ runn -v
runn version 0.91.0
$ curl https://httpbin.org/json -H "accept: application/json"
{
  "slideshow": {
    "author": "Yours Truly",
    "date": "date of publication",
    "slides": [
      {
        "title": "Wake up to WonderWidgets!",
        "type": "all"
      },
      {
        "items": [
          "Why <em>WonderWidgets</em> are great",
          "Who <em>buys</em> WonderWidgets"
        ],
        "title": "Overview",
        "type": "all"
      }
    ],
    "title": "Sample Slide Show"
  }
}
$ runn new --and-run -- curl https://httpbin.org/json -H "accept: application/json"
Error: failed to load runbook /var/folders/7p/g09rrr4n5gjf6c9fwpp5bhjh0000gn/T/runn1459072591/new.yml: scope error: reading files in the parent directory is not allowed. 'read:parent' scope is required: /var/folders/7p/g09rrr4n5gjf6c9fwpp5bhjh0000gn/T/runn1459072591/new.yml
nittanitta

修正確認できました🙇 ご対応ありがとうございます!💪

goemongoemon

runn チュートリアルに記載の内容で include を利用したシナリオを実行したところ、実際の出力が記載の内容と異なっていました。こちらは仕様変更によるものでしょうか?
https://zenn.dev/katzumi/books/runn-tutorial/viewer/include

$ runn --version
runn version 0.99.2
$ USER=katzumi runn run day12/**/*.yml --verbose
=== 既存のシナリオから新しいシナリオを作成する (day12/include.yml)
    --- 指定された件数分、記事一覧を取得します (listArticles) ... ok
    --- 指定された件数分、記事一覧を取得します (listArticles) ... ok
            --- 指定された件数分、記事一覧を取得します (listArticles) ... ok
    --- 1番目の記事の詳細を取得します (showFirstArticle) ... ok
    --- 2番目の記事の詳細を取得します (showSecondArticle) ... ok
=== 単体のシナリオとして定義 (day12/list-articles.yml)
    --- 指定された件数分、記事一覧を取得します (listArticles) ... ok


2 scenarios, 0 skipped, 0 failures
Ken’ichiro OyamaKen’ichiro Oyama

バグ修正しました。報告ありがとうございました!

(なお === の行に ok がないのは仕様変更です)

~/src/github.com/k2tzumi/runn-tutorial (main)> runn --version
runn version 0.99.3
~/src/github.com/k2tzumi/runn-tutorial (main)> env USER=katzumi runn run day12/**/*.yml --verbose
=== 既存のシナリオから新しいシナリオを作成する (day12/include.yml)
    --- 指定された件数分、記事一覧を取得します (listArticles) ... ok
        === 単体のシナリオとして定義 (day12/list-articles.yml)
            --- 指定された件数分、記事一覧を取得します (listArticles) ... ok
    --- 1番目の記事の詳細を取得します (showFirstArticle) ... ok
    --- 2番目の記事の詳細を取得します (showSecondArticle) ... ok
=== 単体のシナリオとして定義 (day12/list-articles.yml)
    --- 指定された件数分、記事一覧を取得します (listArticles) ... ok


2 scenarios, 0 skipped, 0 failures
~/src/github.com/k2tzumi/runn-tutorial (main)>
goemongoemon

バグだったんですね。対応いただきありがとうございます!