Python APIテストがたいてい始まる場所と、それで問題ない理由
requestsとpytestをやめるよう説得されても、耳を貸す必要はありません。テストはごく普通のPythonであり、フィクスチャは組み合わせられ、parametrizeは境界値をきれいに網羅し、チームの誰もが読めます。自分たちが所有するサービスを対象にした、絞り込まれたテストスイートであれば、これは今も優れた答えです。
作業へと変わっていく4つの要素
セッション。 トークンのフィクスチャは、実行の途中で有効期限が切れるまではうまく動きます。切れた瞬間から、更新ロジックを書き、それをワーカー間でキャッシュすることになります。
キャプチャした値。 ある呼び出しで得た識別子が、次の呼び出しに渡ります。依存関係が広がるまではフィクスチャで対応できますが、やがてフィクスチャのグラフそのものが保守の対象になります。
実行順序。 pytestは依存関係に基づいて順序を決めません。そこで順序制御のプラグインに手を伸ばすことになり、序盤のたった1件の失敗が、無関係な赤い結果で画面を埋め尽くします。
4つ目は、並列実行下でのクリーンアップです。pytest-xdistを使い始めた途端、名前のパターンによる削除は安全ではなくなり、teardownは各実行が何を作成したのかを正確に把握しなければならなくなります。
正直に見たコスト
個々の問題は、どれも難しくありません。しかし合わさると、社内フレームワークができあがります。誰も触りたがらないconftest、1人しか理解していないフィクスチャのグラフ、そして赤くなったときに修正ではなく隔離されるテストスイートです。この結末は、あらかじめ織り込んで計画すべき程度には、よくあることです。
専用に作られたレイヤーが引き受けること
どのツールを使うにせよ、APIテストスイートが最初の1年を生き延びられるかどうかは、4つの要素で決まります。期限が切れるセッション、実行時にしか存在しない値、互いに依存し合う呼び出し、そして誰も片付けないレコードです。TestSpriteはこれらをAuto-Authentication、Dynamic Variables、Dependency Chains、Auto-Cleanupと名付けており、 APIテストのドキュメント がその仕組みを解説しています。
カバレッジの出どころも変わります。誰かが思い出したエンドポイントではなく、仕様書から生成されたプラン、あるいは稼働中のサービスを探索して生成されたプランには、手作業では決して書かれないエラーパスや境界ケースが含まれます。
テストをリポジトリに置きたい場合
テストスイートはリポジトリ内に置き、他のコードと同じようにレビューしたい、というチームもあります。それはもっともな好みです。CLIはPythonで書かれたバックエンドテストに対応しており、依存関係はテストごとに宣言し、teardownは独立したケースとして指定します。そのため依存グラフは、フィクスチャの入れ子から暗黙に決まるのではなく、明示的になります。詳細は CLIリポジトリ。
チームを飲み込んだconftest
自分の手に負えなくなったPythonのテストスイートには、見覚えのある形があります。しかもそれは1つのファイルに住み着いています。conftestは、セッションのフィクスチャとベースURLから始まります。次にトークンのフィクスチャ、続いてテストごとにトークンを取得しないためのキャッシュ、そしてxdistのワーカーがキャッシュで競合したためのロック。さらにファクトリ、teardownが何を削除すべきか把握するためのレジストリ、そして一部のファクトリが他のファクトリに依存するための順序マーカーが加わります。
一つひとつのステップはそれ単体では正しく、その結果として、1人しか理解していない600行ができあがります。見分けるサインは、誰かがテストが失敗していると言ったときに、最初の質問が「conftestが変わったのか」になることです。
これをあえて言葉にしておく価値があるのは、それを作ろうという決断が一度も下されないからです。ただ積み上がっていくだけです。どの部分を自分たちで所有し、どの部分は所有したくないのかを早い段階で決めておくほうが、9か月目にその答えを知るより安く済みます。
コードが変わる場所で実行する
リリース前に実行されるPythonのテストスイートは、問題を見つけるのが遅すぎます。重要なのは、プルリクエストごとに実行するという変化です。
方法は2つあり、その選択は実のところ、パイプラインを誰が所有するかの問題です。ダッシュボードから GitHub App を接続する方法なら、リポジトリに一切変更を加える必要がありません。ビルドがすでに発行しているデプロイに反応するからです。一方、 GitHub Actions のステップを追加する方法では、チェックがリポジトリ内に置かれ、ビルドの他の部分と同じようにレビューされます。
TestSpriteが引き受けるので、あなたが抱えなくてよいこと
pytestのテストスイートをインフラに変えてしまう4つの要素は、製品側で処理されます。Auto-Authenticationは、環境ごとにセッションを取得し、更新します。Dynamic Variablesは、あるレスポンスの識別子を次のリクエストへ引き渡します。Dependency Chainsは、各ケースが必要とするものと生成するものから実行順序を導き出すため、失敗が起きても、それに依存するテストは失敗ではなくスキップされます。Auto-Cleanupは、その実行が作成したものだけを正確に削除します。これが並列実行を安全にしています。
カバレッジも別の方向からもたらされます。API Discoveryは、稼働中のサービスが実際に公開しているエンドポイントを列挙し、生成されたプランには、スプリントの終わりには決して書かれないエラーパスや境界値が含まれます。
得られるのは、誰も変更を恐れないテストスイートであり、1人しか理解していないconftestは存在しません。pytestが本当に最適なツールであるユニットテストやロジックのテストには、pytestを使い続けてください。ここで論じているのは、広いカバレッジをどこに置くかということです。
pytestの使用はやめるべきですか?
いいえ。チームが意図して書いたテスト、特にユニットレベルやロジックレベルのテストには使い続けてください。論点は、APIの広いカバレッジをどこに置くかです。
httpxやasyncはどうですか?
並行処理の書き心地は良くなりますが、4つの問題は同じです。セッション、キャプチャした値、実行順序、クリーンアップは、ステートフルなサービスをテストすることに由来する性質であり、HTTPクライアントに由来するものではありません。
テストデータはどう扱えばよいですか?
各テストが必要とするものを作成し、実行に紐づけて、まさにそれだけを削除してください。共有のシードデータは便利に見えますが、特定の順序でしかテストが通らなくなる原因になります。
生成されたカバレッジとpytestは共存できますか?
はい。むしろそれが通常の形です。広さは生成されたカバレッジで、意図して指定したケースはpytestで担当します。
この用途で、Pythonは今も良い選択ですか?
はい。制約になるのは言語ではありません。制約になるのは、テストのインフラをどこまで自分たちで所有したいかです。