TT Lab
はじめる
学ぶ 学習パス コース

テストツール実戦

失敗経路と副作用を分離してテストする

TT Labで続きを見る

目標

ネットワークも実際の待機も使わずに、呼び出し回数・バックオフ・ファイルの保持・保存の失敗を再現します。

なぜ重要なのか

正常なリクエストが1回成功したからといって、境界値や障害からの復旧が保証されるわけではありません。このラボでは、各関数の契約を小さく実装またはテストしたうえで、実際の実行につなげます。コードが存在するかどうかやレポートの文言だけを見るのではなく、結果・例外・保存状態を検査します。前のステップのコードを維持したまま、次のステップへ進んでください。

ステップ

  1. /root/work/test-failures-lab/test_service.pyで、fetchがsendの文字列をそのまま返し、sendをちょうど1回だけ呼び出すことをテストしてください。cacheはtmp_pathの下のpathlib.Pathとして渡します。最初の準備は次のコマンドで行います。
mkdir -p /root/work/test-failures-lab
cp /opt/fixtures/practice_depth/test-failures-lab/* /root/work/test-failures-lab/
cd /root/work/test-failures-lab
  1. /root/work/test-failures-lab/test_service.pyで、最初のsendはTimeoutErrorを、2回目は'fresh'を返すように構成し、fetchが'fresh'を返すことを確認してください。
  2. /root/work/test-failures-lab/test_service.pyで、常にTimeoutErrorになるsendを渡し、合計3回の呼び出しのあとでTimeoutErrorが伝播することを確認してください。
  3. /root/work/test-failures-lab/test_service.pyで、ValueErrorを発生させるsendがちょうど1回だけ呼び出され、同じ種類の例外が伝播することをテストしてください。
  4. /root/work/test-failures-lab/test_service.pyで、すべてのsendがTimeoutErrorのとき、sleepに渡された値がちょうど[0.1, 0.2]であることを検査してください。実際にはスリープしません。
  5. /root/work/test-failures-lab/test_service.pyで、ハングルの文字列で成功させたあと、cacheファイルをUTF-8で読み込み、同じ内容が保存されていることを確認してください。
  6. /root/work/test-failures-lab/test_service.pyで、cacheに'old'をあらかじめ書き込んでから、sendを失敗させ続けてください。例外の後も、ファイルの内容が'old'のままであることを確認します。
  7. /root/work/test-failures-lab/test_service.pyで、sendが成功しても、cacheへの書き込みが失敗したらOSErrorが伝播することをテストしてください。cacheにtmp_pathのディレクトリそのものを渡すと、決定的に書き込みが失敗します。

参考

成功経路の不要な呼び出しを検出する

/root/work/test-failures-lab/test_service.pyで、fetchがsendの文字列をそのまま返し、sendをちょうど1回だけ呼び出すことをテストしてください。cacheはtmp_pathの下のpathlib.Pathとして渡します。最初の準備は次のコマンドで行います。

mkdir -p /root/work/test-failures-lab
cp /opt/fixtures/practice_depth/test-failures-lab/* /root/work/test-failures-lab/
cd /root/work/test-failures-lab

callsリストに記録する関数を注入してください。戻り値だけを見ていると、重複した呼び出しを見逃します。

採点はbash /opt/lab/checks/test-failures-lab/01-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。

1回失敗させたあとで復旧させる

/root/work/test-failures-lab/test_service.pyで、最初のsendはTimeoutErrorを、2回目は'fresh'を返すように構成し、fetchが'fresh'を返すことを確認してください。

iteratorまたは呼び出し回数で順序を制御します。テスト自体では、実際のネットワークに接続しません。

採点はbash /opt/lab/checks/test-failures-lab/02-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。

総試行回数と最後の例外を検査する

/root/work/test-failures-lab/test_service.pyで、常にTimeoutErrorになるsendを渡し、合計3回の呼び出しのあとでTimeoutErrorが伝播することを確認してください。

リトライ3回と合計3回の試行は別物です。この契約は、最初のリクエストを含めて3回です。

採点はbash /opt/lab/checks/test-failures-lab/03-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。

恒久的なエラーのリトライを防ぐ

/root/work/test-failures-lab/test_service.pyで、ValueErrorを発生させるsendがちょうど1回だけ呼び出され、同じ種類の例外が伝播することをテストしてください。

例外の種類だけでなくcallsの長さも検査して、意味のない2回の追加呼び出しを検出します。

採点はbash /opt/lab/checks/test-failures-lab/04-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。

バックオフの数列と最後の待機を確認する

/root/work/test-failures-lab/test_service.pyで、すべてのsendがTimeoutErrorのとき、sleepに渡された値がちょうど[0.1, 0.2]であることを検査してください。実際にはスリープしません。

sleep引数にdelays.appendを渡します。最後の失敗の後には次の試行がないので、待機もあってはいけません。

採点はbash /opt/lab/checks/test-failures-lab/05-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。

戻り値とディスクの内容を一緒に確認する

/root/work/test-failures-lab/test_service.pyで、ハングルの文字列で成功させたあと、cacheファイルをUTF-8で読み込み、同じ内容が保存されていることを確認してください。

成功の結果だけを返してファイルへの書き込みを省いた実装は、戻り値のテストだけでは検出できません。

採点はbash /opt/lab/checks/test-failures-lab/06-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。

ネットワークの失敗で既存のキャッシュが消えないようにする

/root/work/test-failures-lab/test_service.pyで、cacheに'old'をあらかじめ書き込んでから、sendを失敗させ続けてください。例外の後も、ファイルの内容が'old'のままであることを確認します。

準備 → 実行と例外の検査 → ファイルの状態の確認、という順序で構成します。ファイルが存在するかどうかだけを見てはいけません。

採点はbash /opt/lab/checks/test-failures-lab/07-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。

保存の失敗を偽の成功に変えない

/root/work/test-failures-lab/test_service.pyで、sendが成功しても、cacheへの書き込みが失敗したらOSErrorが伝播することをテストしてください。cacheにtmp_pathのディレクトリそのものを渡すと、決定的に書き込みが失敗します。

権限の変更に依存しないエラーを作ってください。成功した文字列の戻り値だけを確認するテストとは、別の責務です。

採点はbash /opt/lab/checks/test-failures-lab/08-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。