プレビューデプロイメントに対してテストすることで、ローカルテストで見逃すものを検出できる理由

Rui Li
プレビューデプロイメントに対してテストすることで、ローカルテストで見逃すものを検出できる理由(カバー)

開発者のラップトップで完璧に動作するコードが、プレビュー環境に到達した瞬間に壊れることがあります。コードが変わったからではありません。環境が変わったのです。そしてローカルテストにはその違いを検出する機会がありませんでした。

ローカルとプレビューの実際の違い

ローカル開発環境とプレビューデプロイメントは、コードエディターの内側からは似ているように見えます。しかし内部は似ていません。

環境変数は異なります。特定の機能にのみ重要な方法で異なることもあります。ローカルでテストモードで動作する決済プロバイダーとステージングで若干異なるテストモード、レート制限が異なるAPIキー、デフォルト値が異なるフィーチャーフラグなど。データベースも異なります。ローカル開発は通常、厳選された少数のテストレコードに対して実行されます。プレビューデプロイメントがステージングデータベースに接続されている場合、実際のデータ量と形状に近いものに対して実行されます。これにより、厳選されたローカルデータセットには含まれないクエリパフォーマンスの問題やデータのエッジケースが表面化します。

ネットワーク条件も異なります。同じマシン上で動作するサービスへのローカルAPIコールは即座に返ります。デプロイされたプレビュー環境からの同じコールは実際のネットワークを経由し、実際のレイテンシーが発生します。これがまさに、ローカルテストでは何もかもが速く解決しすぎて悪い形でインターリーブされなかったために発生しなかったレースコンディションを露わにする条件です。

これはローカル開発の欠陥ではありません。単に異なる環境であり、一方の環境の前提に依存するコードが、もう一方の環境でサイレントに失敗する可能性があるということです。

デプロイ後にのみ現れる障害

特定のカテゴリのバグは、特にプレビューデプロイメントのバグです。開発者のマシン以外の場所でコードが実行されるまで見えません。

ビルド時とランタイムの違い。ローカルでホットモジュールリローディングで動作するコードは、特にコードスプリッティングと遅延ローディングに関して、本番スタイルのフルビルドを経ると異なる動作をすることがあります。

クロスオリジンとCookieの動作。プレビューデプロイメントは通常、独自のサブドメインで動作します。これにより、Cookie、CORSポリシー、認証リダイレクトの動作がlocalhostと比較して変わります。ローカルで完璧に動作する認証フローが、このドメインの違いにより特定的に壊れることがあります。

実際のレイテンシー下でのタイミング。APIコールが解決される前に楽観的に更新するUIは、コールがほぼ即座に解決されるローカルでは正しく見えますが、実際のネットワークレイテンシーが介在すると不正確な状態のフラッシュが現れます。

ソースコードだけでなく、プレビューをテストする

これらの問題を検出するには、ソースコードを単独でではなく、デプロイされた環境のように動作するものをテストする必要があります。ここで、コードを読んで正確性を推測するツールとはTestSpriteのアプローチが異なります。エクスプロレーションエージェントは実際に動作しているプレビューURLにアクセスし、実際の訪問者と同じように操作します。

他の検証ツールはコードを読んで推測します。TestSpriteはあなたのアプリを開いて実際に使います。

GitHub Actions連携を通じて接続されたTestSpriteは、PRのプレビューデプロイメントが利用可能になり次第、自動的に実行されます。PRがマージされた場合に実際にリリースされるビルド、つまりすべての環境固有の動作が保持された状態でテストします。

シナリオ:オークションプラットフォームとプレビューにのみ存在したタイムゾーンバグ

小規模なチームがオンラインオークションプラットフォームを構築しています。各出品には終了時刻が設定されており、その時刻を過ぎた瞬間に入札が締め切られます。開発者はClaude Codeを使って終了時刻の計算ロジックをリファクタリングし、フロントエンドからバックエンドに移行しました。これにより、訪問者のローカルクロック設定に関わらず、すべてのクライアントで一貫したカウントダウンが表示されるようになります。

ローカル環境では、すべてが正常に動作します。開発者のマシンはローカル開発時にバックエンドサーバーが動作するタイムゾーンと同じに設定されているため、計算された終了時刻はあらゆる手動確認で期待どおりの値と一致します。

PRがVercelプレビューデプロイメントに対してTestSpriteをトリガーします。このデプロイメントは、開発者のローカルタイムゾーンではなくUTCで設定されたインフラ上で動作します。探索エージェントはテスト出品を作成し、終了時刻を設定した上で、カウントダウンと実際の入札締め切りを検証します。テストの結果、出品が約5時間早く終了することが判明しました。リファクタリングされたバックエンドロジックは、ローカル時刻とサーバー時刻がたまたま一致する環境でしかテストされていなかったタイムゾーンオフセットを適用していたのです。

失敗レポートには、何が起きたかが正確に記載されています。期待される終了時刻、実際の終了時刻、そして5時間の差異です。この具体性は、ソースコードではなくデプロイ済みプレビューに対してテストを実行したことから直接得られたものです。このバグは、ローカル時刻とサーバー時刻が一致する環境では一切検出できませんでした。

開発者はオフセット計算を修正し、サーバーのローカル時刻を前提とするのではなく、一貫してUTCを使用するように変更します。修正をプッシュすると、GitHub Actionsワークフローが更新されたプレビューに対して再実行され、終了時刻がタイムゾーンをまたいで一致することが確認されます。

プレビューテストを追加作業ではなくデフォルトの工程にする

このカテゴリのバグに対する解決策は、より慎重なローカルテストではありません。どれほど徹底したローカルテストであっても、コードをデプロイして初めて現れる問題は検出できません。解決策は、プレビューデプロイメントのテストをマージ前のデフォルトチェックポイントにすることです。開発者が「リスクのある変更かもしれない」と判断したときに手動で実施するものではなく、標準的な工程として組み込む必要があります。

GitHub Actionsインテグレーションを通じて、このチェックはすべてのPRに対して自動的に実行されます。デプロイ後に異なる挙動を示す可能性がある変更を開発者が事前に予測する必要はありません。Claude CodeやCursorを通じてセッション内トリガーも利用しているチームであれば、「TestSpriteでこのプロジェクトをテストして」とプレビューURLを直接指定することで、PRを開く前に同じカテゴリの不具合を検出できます。

まとめ

プレビューデプロイメントは、単にローカル開発環境をどこか別の場所で動かしたものではありません。それ固有のタイムゾーン、環境変数、ネットワーク条件があり、それらの違いがあるからこそ存在するバグがあります。

TestSpriteは、ソースコード単体ではなく実際にデプロイされたプレビューをテストします。これこそが、ローカルテストでは構造的に検出できない不具合を捉える理由です。

TestSpriteをプレビューデプロイメントに接続して、環境固有のバグが本番環境に到達した後で発覚するという状況をなくしましょう。