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

多くのテストスイートは楽観的です。エンジニアが想定した入力——有効なフォームデータ、期待されるAPIペイロード、通常のユーザーフロー——のみをテストします。ファズテストは意図的に悲観的なアプローチを取ります。ランダムで不正な入力やエッジケースの入力を生成してアプリケーションに送り込み、何が壊れるかを確かめます。
ファズテストが発見するバグは実際のバグです——通常、セキュリティ脆弱性・メモリ安全性の問題・クラッシュ条件であり、アプリケーションが何をすべきかを考えているテスターよりも先に、悪意あるユーザーによって発見されるものです。
ファジングの仕組み
ファザーは有効なサンプルを元に変異させた入力のストリームを生成します。JSONのAPIエンドポイントをファジングする場合、ファザーは有効なリクエストボディを取り、系統的に変異させていきます。文字列を整数に置き換える、予期しない文字を挿入する、スカラーが期待される箇所に配列を送る、必須フィールドを省略する、最大長の値を持つフィールドを送る、アプリケーションが想定する以上に深くネストされた構造を送る、といった操作が行われます。
生成された各入力に対して、ファザーはアプリケーションのクラッシュ・タイムアウト・エラー状態・予期しない動作パターンを監視します。興味深い動作を引き起こした入力は保持されてさらに変異が加えられます。通常のエラーハンドリングを生じさせた入力は破棄されます。時間をかけて、ファザーは異常なコードパスを実行する入力のコーパスを構築していきます。
ファジングの背景にある洞察は、コードが期待される入力を前提に考えた人間によって書かれているという点です。入力バリデーションのバグが発生するのは、開発者がファザーの生成する特定の不正な入力を想定していなかった場合に限り起こります。
ファジングが発見するもの
セキュリティ脆弱性が主なターゲットです。SQLインジェクション・クロスサイトスクリプティング・パストラバーサル・バッファオーバーフロー・フォーマット文字列脆弱性はいずれもファジングが確実に発見できるパターンを持っています。これらは機能テストスイートには現れません。機能テストは有効な入力を使用するからです。これらは悪意ある入力や不正な入力が防御的に書かれていないコードパスを実行したときに初めて現れます。
パースの失敗も同様に一般的なファジングの発見事項です。ファイルパーサー・画像デコーダー・プロトコル実装、そして外部から供給される構造化データを処理するあらゆるコードは、パースのエッジケースに対して脆弱です。不正なフォントテーブルを持つPDFでクラッシュするパーサー。破損したヘッダーを持つファイルでバッファをオーバーフローさせる画像ライブラリ。これらの失敗は機能テストでは見えず、ファジングで簡単に発見できます。
APIの堅牢性の失敗——無効な入力に対して400エラーではなく500エラーを返すエンドポイント、エラーレスポンスにスタックトレースを漏洩するエンドポイント、予期しないContent-Typeに対して異なる動作をするエンドポイント——はファジングによって検出され、整形されたリクエストのみを送るテストでは見落とされます。
ファジングと機能テストは異なる失敗の次元をカバーする
ソフトウェアテストは2つの異なる質問に答えるものだと考えてください。機能テストとE2Eテストは「アプリケーションはあるべき動作をしているか?」という質問に答えます。ファズテストは「アプリケーションは受け取るべきでないものを安全に処理できるか?」という質問に答えます。
自律型E2Eテスト——コードベースと製品要件からエージェントがテストを生成・実行する——は1つ目の質問を包括的にカバーします。ユーザーフロー・APIの動作・認証・エラーハンドリング・UIの一貫性を検証します。すべてのテストは有効でリアルな入力を使用します。アプリケーションが仕様通りに動作することを確認するのが目的だからです。
ファジングは2つ目の質問をカバーします。仕様には含まれない無効・予期しない・敵対的な入力の空間を探索します。この2つのアプローチの交差部分は小さく、組み合わせたカバレッジはどちらか一方よりも大幅に広くなります。
機能テストのみを実施しているチームは、不正な入力によるセキュリティエクスプロイトやクラッシュ条件に対して脆弱です。ファジングのみを実施しているチームは、アプリケーションが実際に正しく動作するかどうかを検証せずに堅牢性のみをテストしています。両方が必要です。
CIへのファジングの統合
ファズテストは決定論的なテストとは異なる方法でCIに統合されます。完了まで実行して結果をアサートするのではなく、ファズテストは固定の時間枠で実行され、その間に発見されたクラッシュやアサーションの失敗を報告します。毎PRに対して5分間のファジングを実行することで、CIの所要時間を管理可能な範囲に抑えながら、意味のある入力クラスをカバーできます。
包括的なカバレッジのための実践的なCIパイプラインは次のようになります。まず自律型E2Eテストが実行され、アプリケーションの定義された動作が正しいことを検証します。ファズテストは並行して実行され、入力バリデーションとセキュリティのギャップを探索します。合わせて10分以内に完了し、PRは「正しく動作するか」と「悪用に対して安全に処理できるか」の両方をカバーする結果を得られます。
ファジングを始めるには
現在ファジングを実施していない場合は、APIエンドポイント——特にユーザーが入力する構造化データ(JSON・XML・ファイルアップロード)を受け付けるもの——から始めてください。これらは入力バリデーション失敗に対してリスクが最も高く、ファジングも最も容易な対象です。
すでにすべてのPRで自律型E2Eテストを実行しているチームにとって、ファズカバレッジの追加は自然な次のステップです。E2Eテストはアプリケーションが動作することを保証します。ファズテストは誰かが壊そうとしたときに壊れないことを保証します。どちらもCIで実行すべきであり、どちらも失敗時にマージをブロックすべきであり、合わせることで意図された動作から敵対的な耐性まで、ソフトウェア品質の全領域をカバーします。