ファズテストとE2Eテスト:ソフトウェア品質の2つの側面

Rui Li
ファズテストとE2Eテスト:ソフトウェア品質の2つの側面 カバー

多くのテストスイートは楽観的です。エンジニアが想定した入力——有効なフォームデータ、期待されるAPIペイロード、通常のユーザーフロー——に対してテストします。ファズテストは意図的に悲観的です。ランダムで不正な境界値の入力を生成し、アプリケーションに対して投げかけて何が壊れるかを確認します。

ファズテストが発見するバグは実際のバグです——通常はセキュリティの脆弱性、メモリ安全性の障害、クラッシュの条件であり、アプリケーションが何をすべきかを考えているテスターではなく、悪意のあるユーザーによって先に発見されるものです。

ファジングの仕組み

ファザーは有効なサンプルから派生した変異入力のストリームを生成します。JSON APIエンドポイントをファジングする場合、ファザーは有効なリクエストボディを受け取り、体系的に変異させ始めます。文字列を整数に置き換える、予期しない文字を挿入する、スカラーが期待される場所に配列を送信する、必須フィールドを省略する、最大長の値を持つフィールドを送信する、アプリケーションが想定するよりもはるかに深くネストした構造を送信するなどです。

生成された各入力に対して、ファザーはクラッシュ、タイムアウト、エラー状態、または予期しない動作パターンがないかアプリケーションを監視します。興味深い動作を引き起こした入力は保持され、さらに変異されます。通常のエラーハンドリングを生じさせた入力は破棄されます。時間が経つにつれて、ファザーは通常とは異なるコードパスを実行する入力のコーパスを蓄積します。

ファジングの背後にある洞察は、コードは期待される入力について推論する人間によって書かれているということです。入力検証のバグは、開発者がファザーの生成する特定の不正な入力について考えなかったからこそ発生します。

ファジングが発見するもの

セキュリティの脆弱性が主要なターゲットです。SQLインジェクション、クロスサイトスクリプティング、パストラバーサル、バッファオーバーフロー、フォーマット文字列の脆弱性はいずれも、ファジングが確実に発見するシグネチャを持っています。これらは機能テストスイートには現れないバグです。機能テストは有効な入力を使用するからです。これらは悪意のある、または不正な入力が防御的に書かれていないコードパスをトリガーするときに現れます。

パースの失敗も同様によく見られるファジングの発見です。ファイルパーサー、画像デコーダー、プロトコルの実装、および外部から提供された構造化データを処理するコードはすべて、パースのエッジケースに対して脆弱です。不正なフォントテーブルでクラッシュするPDFパーサー。破損したヘッダーを持つファイルでバッファをオーバーフローする画像ライブラリ。これらの失敗は機能テストには見えず、ファジングで簡単に発見できます。

APIの堅牢性の失敗——無効な入力に対して400エラーではなく500エラーを返すエンドポイント、エラーレスポンスにスタックトレースを漏洩するエンドポイント、予期しないコンテンツタイプに対して異なる動作をするエンドポイント——はファジングで検出されますが、整形式のリクエストのみを送信するテストでは見逃されます。

ファジングと機能テストは異なる障害の次元をカバーする

ソフトウェアテストを2つの異なる質問への答えとして考えてください。機能テストとE2Eテストは「アプリケーションは何をすべきかを実行しているか?」という質問に答えます。ファズテストは「アプリケーションは受け取るべきでないすべてのものを安全に処理できるか?」という質問に答えます。

自律的なE2Eテスト——エージェントがコードベースと製品要件からテストを生成・実行する——は最初の質問を包括的にカバーします。ユーザーフロー、APIの動作、認証、エラーハンドリング、UIの一貫性を検証します。目標はアプリケーションが仕様どおりに動作することを確認することなので、すべてのテストは有効でリアルな入力を使用します。

ファジングは2番目の質問をカバーします。仕様には含まれない無効、予期しない、敵対的な入力の空間を探索します。この2つのアプローチの交差点は小さいです。組み合わせたカバレッジはどちらか単独よりも大幅に広くなります。

機能テストのみを行っているチームは、不正な入力によるセキュリティの悪用やクラッシュの条件に対して脆弱です。ファジングのみを行っているチームは、アプリケーションが実際に正しく動作することを検証せずに堅牢性をテストしています。両方が必要です。

CIへのファジングの統合

ファズテストは決定論的なテストとは異なる方法でCIに統合されます。完了まで実行して結果をアサートするのではなく、ファズテストは固定された時間バジェットで実行され、その間に発見されたクラッシュやアサーションの失敗を報告します。すべてのPRに対する5分のファジングランは、CIの実行時間を管理可能に保ちながら、意味のあるクラスの入力を検出します。

包括的なカバレッジのための実用的なCIパイプラインは次のようになります。自律的なE2Eテストが最初に実行され、アプリケーションの定義された動作が正しいことを検証します。ファズテストは並行して実行され、入力検証とセキュリティのギャップを探索します。両方合わせて10分以内に完了し、PRは「正しく動作する」と「悪用を安全に処理する」の両方をカバーした結果を得られます。

ファジングの始め方

現在ファジングを行っていない場合は、APIエンドポイント——特にユーザーが提供する構造化データ(JSON、XML、ファイルアップロード)を受け入れるもの——から始めてください。これらは入力検証の失敗に対して最もリスクが高く、最もファジングしやすいサーフェスです。

すべてのPRで自律的なE2Eテストをすでに実行しているチームにとって、ファズカバレッジの追加は自然な次のステップです。E2Eテストはアプリケーションが動作することを保証します。ファズテストは誰かが壊そうとしたときに壊れないことを保証します。両方ともCIで実行し、失敗時にはマージをブロックし、そして組み合わせることで、意図した動作から敵対的な耐性まで、ソフトウェア品質のフルスペクトラムをカバーします。