この記事は Uzabase Advent Calendar 2025 10日目です1。
このカレンダー、ぜひ見ていただきたいですね。
「どや俺の仲間すごいやろ!!」2
はじめに
七尾百合子さん、お誕生日 267日目 おめでとうございます! nikkieです。
ユーザベースが公開している実装から、私が「どやすごいやろ」と思ったポイントを紹介します。
目次
- はじめに
- 目次
- playtest2
- playtest2 の examples は、実行できる例になっている!
- BeforeSuite でテスト対象の API を起動
- Python でやってみる ー デーモンスレッド!
- 終わりに
- P.S. なぜ Python ポート?
playtest2
ユーザベースでは Gauge を使って End-to-End のテストを書いています3。
自然言語で仕様書としてテストを書けるのがいい感じです。
各種プログラミング言語に対応しています。
Python ではこんな感じ
ユーザベースは、Gauge でよく書く step をライブラリ化して公開しています4。
GitHub - uzabase/playtest
## /pingに対するテスト * パス"/ping"に * メソッド"GET"で * メディアタイプ"application/json"で * リクエストを送る * レスポンスのステータスコードが * 整数値の"200"である
これが手に馴染む感じだったので、私は今年 playtest2 を Python にポートしました(※勝手にやってる非公式な活動です)
https://pypi.org/project/playtest2/
playtest2 の examples は、実行できる例になっている!
ポートする中で、playtest2 の examples が興味を引きました。
https://github.com/uzabase/playtest2/tree/v0.0.9/examples
GitHub Actions でこの examples も実行しています。
https://github.com/uzabase/playtest2/blob/v0.0.9/.github/workflows/test.yaml#L22-L43
steps: - uses: actions/checkout@v4 - uses: sdkman/sdkman-action@b1f9b696c79148b66d3d3a06f7ea801820318d0f id: sdkman - name: Gauge install run: | curl -SsL https://downloads.gauge.org/stable | sh - name: Run tests run: | export JAVA_HOME=$(sdk home java 21.0.1-tem) mvn clean install -Dgpg.skip --no-transfer-progress cd examples/${{ matrix.environment }} mvn test --no-transfer-progress
あれ、mvn testしかしていない?
テスト対象の API は、どこで起動しているんでしょう?
BeforeSuite でテスト対象の API を起動
class Steps { @BeforeSuite fun beforeSuite() { App.startServer() }
BeforeSuiteフックを使い、テストスイートの実行前に、APIを起動しています7!
https://docs.gauge.org/writing-specifications?os=macos&language=java&ide=vscode#execution-hooks
ふだんの開発ではテスト対象のAPIを別ターミナルで手動で立ち上げるので、フックを使ってコードからAPIを起動しているのが私には盲点でした
Python でやってみる ー デーモンスレッド!
playtest2-python でもこのたび導入しました。
FastAPI のアプリを @before_suite で起動します(uvicorn.run())。
uvicorn.run()をそのまま実行すると、起動したサーバがブロッキングしてテストが実行されませんでした。
ここでスレッドの出番かと膝を打ちました。
https://github.com/ftnext/playtest2-python/blob/4fa5d2257822288fe4f7c08dd68a62bfec1c7407/example/step_impl/execution_hooks.py#L16-L19
@before_suite def start_app(): server_thread = threading.Thread(target=uvicorn.run, args=(app,), kwargs={"port": 8000}, daemon=True) server_thread.start()
テストを実行した後、アプリをどう終了するかですが、これはuvicornを操作するのではなく、デーモンスレッドにすることで実現できました。
https://docs.python.org/ja/3/library/threading.html#thread-objects
スレッドには "デーモンスレッド (daemon thread)" であるというフラグを立てられます。
このフラグには、残っているスレッドがデーモンスレッドだけになった時に Python プログラム全体を終了させるという意味があります。
以上で、本家 playtest2 と同様に、example を実行できるようになりました!
終わりに
ユーザベースが公開するライブラリ playtest2 で、テスト対象 API を明示的に起動する代わりに BeforeSuite フックを使って起動していた話でした。
Python にポートしようとすると「無茶かもしれないが全部真似たい」と思うもので、それに突き動かされた結果、今回はデーモンスレッドという技術的な学びがありました。
P.S. なぜ Python ポート?
今年の DjangoCongress JP 2025(オンライン開催)がきっかけです。
先行発表の FastAPI アプリを私が主張する設計へと変更したのですが、前後で動きを変えていないことの確認に playtest2 を使いたくなり、ポートし始めました〜
- 過去にも参加しています。2023 2021 ↩
- 『ハイキュー‼︎』の北さんのセリフ(33巻)↩
- 最近エムスリーさんでも記事をお見かけして嬉しかったです ↩
- 「ここ最近ではplaytestという、一般的なGauge Stepを定義したものをライブラリ化して利用しています。」CIを改善し続けて見えてきた、高速で安定したCIを実現するためのTips↩
- 「E2E には Gauge や Playtest2、WireMock を利用していて、」 ↩
- 第2世代の作者さまはこちらの発表の方です ↩
-
AfterSuiteフックで落としています。https://github.com/uzabase/playtest2/blob/v0.0.9/examples/simple-api-test/src/test/kotlin/Steps.kt#L32-L36↩