「個人開発してるWebサービス」というのは Pixela のことで、runn とは @k1loW さんが開発しているオペレーション自動化ツール/パッケージです。
Pixela は、そのユーザーインターフェースとして基本的に Web API のみを提供しているサービスで(サービスを利用するための各種操作は基本的にすべて Web API に対する HTTP リクエストによって行う必要がある)、現在そのローンチから6年目を迎えるサービスです。
ありがたいことに、世界中のユーザー(特に、プログラミング初学者の方によくご利用いただいているようです)に継続的に使っていただけているサービスになっており、登録ユーザー数はもうすぐ7万人に到達しようとしているところです。開発・メンテナンスに係る私の人件費を除けば、黒字運営を続けることもできています。
一方の runn については、以前より何かのきっかけでその存在だけは知っていました。runn の主なユースケースとしては、Zenn で販売されているオンラインブックでの記述を引用させていただくと、概ね以下のようなものとなります。
runnが想定している主なユースケースは次のようなものになります。 1. APIのシナリオテスト(HTTP/gRPC) 2. ローカルやリモートへの任意のコマンドやHTTP/gRPCリクエスト、データベースクエリを実行可能なワークフローエンジン 3. データベース操作等のオペレーションの実行と記録 Runbook Automationとしては2がもっともそれらしいユースケースですが、現在runnの開発メンバーが力を入れているのは1になります。 なので、runnはAPIシナリオテスティングツール・パッケージとも言えるでしょう。 -- runnクックブック https://zenn.dev/k1low/books/runn-cookbook/viewer/about Chapter01 runnとは より
「APIシナリオテスティングツール・パッケージ」。前述のようなサービスである Pixela のテストのために使うのにはまさにぴったりのツールなんじゃないかと思っていて、今回ようやくこれに取り組むための時間を取ることができ、また最低限テストとして意味のある形で組み込むところまでできたので、一区切りとして、その諸々をまとめておきたいと思います。
やってみてどうなったか
いきなり結論、というか、Pixela というサービスに対して runn を使うことでどのような嬉しいことがあったか、というのをまとめます。
1. すべての Web API をひととおり使うようなシナリオテストを、自動で実行できる状態になった
「ハッピーパステスト」というか「スモークテスト」というか、「多くのユーザーが行う基本的な操作が、今も問題なく動く状態にあること」を常に手軽に確認できる状態にしたいなぁ、と、以前から考えていたのですが、今回まさしくそのような状態をつくりあげることができました。
Pixela の Web API に対しては様々なパラメータを渡すことができるので、決して「1つのAPIに対するテストが1つのシナリオで網羅できる」というわけではないのですが、細かいパラメータの組み合わせとそれに対応する挙動については単体レベルのテストで別途カバーできている(すべき)なので、そこは割り切って活用しています。
「そもそもルーティングを壊してしまっていないか?」とか「デプロイ先の各種疎通状態に問題は起きていないか?」とか、あとは「API間をまたいだ操作で整合性が崩れてしまっていないか?」とか、そういったレベルでの確認はこれまで手動で、しかも極めて限定的な範囲に対してしか確認できていなかったので、今回この仕組みを構築することができて本当に満足しています。
基本的なユースケースは runn でテストされている状態にできたので、今後変更を加えるときでも、これまでよりも自信を持って行うことができそうです。
こちらこそありがとうございました!おかげさまで一気にもろもろが安心な状態になり、今後の開発に際しても心強くなりました!💪
— a-know | Daisuke Inoue (@a_know) January 24, 2024
2. runn によるAPIシナリオテストが、リポジトリに対するpushごとに自動で行われるようにできた
GitHub Actions も用意されていたので、極めてスムーズに導入することができました。
よき https://t.co/Ec5wmYDaXm pic.twitter.com/qZ7UtUTRSo
— a-know | Daisuke Inoue (@a_know) 2024年1月24日
イヤッハー!!🎉🎉 pic.twitter.com/YpL4WxgCeS
— a-know | Daisuke Inoue (@a_know) 2024年1月24日
個人的に、 debug
オプションは常に有効でいいな、と思っています。
3. 潜在バグやドキュメンテーションされていなかった仕様をあぶり出すことができた
上述のとおり、サービスローンチ以降6年目に突入しているということもあり、結構 "枯れている" 部分も多くなってきていたので、想定外のバグに出くわすといったことも(新機能部分以外では特に)最近減っていたのですが、今回 runn の runbook を組み上げる過程で、みごとにそれをあぶり出すことができました......!
runn でテストを作る過程で、潜在バグを見つけることができた...。。
— a-know | Daisuke Inoue (@a_know) January 18, 2024
実に5年モノのバグ。ひえぇ😱😱
これこそ、単体テストだけじゃなくこのレベルのテストも行うという意味のうちのひとつだよなぁと実感しましたし、むしろこういう取り組みをしてなければおそらく今後も自力で気がつくことはできなかっただろうなぁ、とも思いました。
また、ドキュメンテーションされていなかった(おそらく開発当初は意識できていたであろう)Pixela の仕様に気付かされた、という出来事もありました。
そして今回は、作った人も失念してたような微妙な仕様を思い出させてくれた。。一瞬、またバグか!と思ったくらいだった😂 https://t.co/fxgjpAJYqn
— a-know | Daisuke Inoue (@a_know) January 24, 2024
尊い、尊すぎる......! 以降の追加開発の際には、RDD(runn-driven development)にも挑戦していきたいですね。
runn を始めてみるにあたり、事前に確認したもの
冒頭でも紹介したオンラインブックである「runn クックブック」には、一通り目を通しました。そもそも最初は購入する気マンマンだったのですが、購入しないと読めない Chapter に自分が必要としそうな情報がなかったので、ありがたく無料で読ませていただきました。
あとは、リポジトリの README も一通り確認しました。情報が充実しまくっていて素晴らしいなと思いました。
自分的tips
基本的には、上記のクックブックと README で困ることはなかったのですが、自分的に少しつまづいたような箇所についてtips的にまとめておきます。
前のステップの結果などから得られる文字列の一部を編集した上で、後続のステップで使用したいとき
Pixela ではまさにこのようなことを行う必要のあるシナリオが多くて、例えば、「ある API にリクエストした結果得られる文字列 2024-01-23
を 20240123
にした上で、次の API に渡したい」、といったことができないと、使えたくても使えないな......と思っていました。
クックブックや README からもそれに関連する情報を見つけることができなかったので、以下のようにつぶやいてみたところ、
前のステップの結果をそのまま使うことはできるようだけど、一部をreplaceしたりconcatした上で後続で使う、とかはできるのかな? {{}} の中ならGoのコードが書ける? #runn https://t.co/7rTFct7QQj
— a-know | Daisuke Inoue (@a_know) January 7, 2024
@k1LoW さんが救いの手を差し伸べてくれました。ありがとうございますありがとうございます。
https://t.co/A3QFGHkxyA らへんを駆使したらいけるかも?です
— k1LoW (@k1LoW) January 11, 2024
あとは組み込まれている https://t.co/FJFmolhMHu を使うとかですかねー
— k1LoW (@k1LoW) January 11, 2024
今回のケースでは、あらかじめ runn に組み込んでいただいていた expr がドンピシャで、おかげで難なくシナリオを作ることができました。
たとえば、今回作った runbook には以下のような記述がところどころに含まれています。
body: application/json: - date: "{{string(int(replace(steps[xx].res.body.maxDate, '-', '')) - 1)}}"
途中でテストが失敗した場合でも、毎回実行してほしいステップがある場合
いわゆる before/after や setup/teardown 相当のことを runn でやりたいときにどうすればいいの?というものですね。今回の自分のケースの場合、テストのために作成したユーザーは最後には必ず削除しておきたい、といったニーズがありました。
runn で、途中のステップが失敗した場合でも必ずこのステップだけは実施する、みたいなのってできるのかな? 👀 #runn
— a-know | Daisuke Inoue (@a_know) January 18, 2024
これについてはすでに issue が作成され議論も進んでいるものの、まだ機能としては実装されていないそうです。そのため今回は force
を使うことにしました。
これは、すべてのステップを毎回必ず実施させるためのものです。仮にシナリオの冒頭のステップでテストが失敗してしまい、それによってそれ以降のステップもすべて失敗することがわかっていたとしても、teardown 相当のステップも含めたのこりすべてのステップも愚直に実行する、という挙動になるので、少しスマートではない感じはありますが、少なくとも teardown 相当の処理が漏れる、ということは無くすことができました。
Web API に対するテストのための runbook を作るのには runn new --and-run
が便利!
とても便利でした。これがなかったらここまでのスピードで今回のテストを組み上げることはできなかったでしょうし、そもそも途中で心が折れてしまっていた可能性もあったな、とすら思います。
おまけ・初めて GitHub Sponsor でスポンサーになってみた
以上、個人的なニーズもあって k1LoW/runn を使ってみたことについてまとめてみました。
ここからは余談なのですが、今回の取り組みを通じて runn に関するあらゆること・ものに対して深い感謝の気持ちが発生し、端的に言うと大好きになったため、少額・ワンタイムではありますが、GitHub Sponsor による寄付を行ってみました。
💖 I'm sponsoring @k1LoW because k1LoW/runn is a great tool! https://t.co/e0TJyueLMW
— a-know | Daisuke Inoue (@a_know) January 24, 2024
GitHub Sponsor を使うのは今回初めてだったのですが、特につまづくことなくスムーズにできましたし、なにより特別な "いい気持ち" になることができて良かったです。こういう形での contribute も、今後はやっていきたいなと思いました。