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

テストツール実戦

設定テストのグローバル状態を分離する

TT Labで続きを見る

目標

誤った環境値・デフォルト値・スナップショット・機密フィールドの露出というリグレッションを検証します。

なぜ重要なのか

先に実行したテストが環境変数を変更したために、後のテストが失敗しました。テストの順序を固定すると問題は隠れましたが、実際のアプリで設定の打ち間違いまで正常なデフォルト値に変わってしまうバグは残りました。入力の分離と、厳密なパースを別々に確認する必要があります。

ステップ

  1. /root/work/test-settings-isolation-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: parse_bool(value)は、大文字小文字を区別しないtrueまたはfalseだけをboolとして返します。空白が付いている場合や文字列でない場合はValueErrorです。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。

最初に1回だけ準備してください。既存のファイルは上書きしません。

mkdir -p /root/work/test-settings-isolation-lab
test -e /root/work/test-settings-isolation-lab/service.py || cp /opt/fixtures/ten_labs/test-settings-isolation-lab/service.py /root/work/test-settings-isolation-lab/service.py
test -e /root/work/test-settings-isolation-lab/test_service.py || cp /opt/fixtures/ten_labs/test-settings-isolation-lab/test_service.py /root/work/test-settings-isolation-lab/test_service.py
cd /root/work/test-settings-isolation-lab
  1. /root/work/test-settings-isolation-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: parse_port(value)は、ASCIIの数字だけの文字列をintに変換し、1–65535なら返します。それ以外はValueErrorです。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。

  2. /root/work/test-settings-isolation-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: parse_timeout(value)は、文字列をfloatに変換し、0より大きく30以下の有限の値だけを返します。それ以外はValueErrorです。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。

  3. /root/work/test-settings-isolation-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: required_token(env)は、TOKENが文字列で、stripした後に空でないとき、stripした値を返します。存在しない、または空の値はValueErrorです。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。

  4. /root/work/test-settings-isolation-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: load_settings(env)は、service=envのSERVICE(欠落時は'api')、debug=parse_bool(DEBUG、欠落時は'false')、port=parse_port(PORT、欠落時は'8000')、timeout=parse_timeout(TIMEOUT、欠落時は'5')、token=required_tokenの辞書です。空のSERVICEはValueErrorです。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。

  5. /root/work/test-settings-isolation-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: public_settings(settings)は、serviceとdebugだけを持つ新しい辞書です。元のデータは変更しません。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。

  6. /root/work/test-settings-isolation-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: snapshot(env)は、load_settingsの結果を返します。呼び出した後でenvを変更しても、返された設定は変わりません。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。

  7. /root/work/test-settings-isolation-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: create_app(env)は、snapshotをただちに読み取り、誤った設定ならValueErrorでアプリの生成を失敗させます。GET /infoはpublic_settingsだけを返します。別のenvで作ったアプリ同士は、設定を共有しません。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。

参考

ブール値を明示的にパースする(テスト)

/root/work/test-settings-isolation-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: parse_bool(value)は、大文字小文字を区別しないtrueまたはfalseだけをboolとして返します。空白が付いている場合や文字列でない場合はValueErrorです。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。

最初に1回だけ準備してください。既存のファイルは上書きしません。

mkdir -p /root/work/test-settings-isolation-lab
test -e /root/work/test-settings-isolation-lab/service.py || cp /opt/fixtures/ten_labs/test-settings-isolation-lab/service.py /root/work/test-settings-isolation-lab/service.py
test -e /root/work/test-settings-isolation-lab/test_service.py || cp /opt/fixtures/ten_labs/test-settings-isolation-lab/test_service.py /root/work/test-settings-isolation-lab/test_service.py
cd /root/work/test-settings-isolation-lab

bool('false')はTrueです。許可する2つの文字列を直接比較してください。実装ファイルは修正しません。pytest.raisesで期待する例外を確認し、正常な結果には具体的な期待値をassertしてください。

保存後、bash /opt/lab/checks/test-settings-isolation-lab/01-contract.shで確認してください。

ポートの範囲を検査する(テスト)

/root/work/test-settings-isolation-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: parse_port(value)は、ASCIIの数字だけの文字列をintに変換し、1–65535なら返します。それ以外はValueErrorです。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。

整数への変換に成功しても、有効なポートの範囲だという意味ではありません。実装ファイルは修正しません。pytest.raisesで期待する例外を確認し、正常な結果には具体的な期待値をassertしてください。

保存後、bash /opt/lab/checks/test-settings-isolation-lab/02-contract.shで確認してください。

時間制限を有限の値にする(テスト)

/root/work/test-settings-isolation-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: parse_timeout(value)は、文字列をfloatに変換し、0より大きく30以下の有限の値だけを返します。それ以外はValueErrorです。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。

NaNは通常の比較で期待と異なる動作をするので、isfiniteを確認します。実装ファイルは修正しません。pytest.raisesで期待する例外を確認し、正常な結果には具体的な期待値をassertしてください。

保存後、bash /opt/lab/checks/test-settings-isolation-lab/03-contract.shで確認してください。

必須の機密値の欠落を拒否する(テスト)

/root/work/test-settings-isolation-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: required_token(env)は、TOKENが文字列で、stripした後に空でないとき、stripした値を返します。存在しない、または空の値はValueErrorです。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。

欠落した必須の機密値を、例のデフォルト値で置き換えません。実装ファイルは修正しません。pytest.raisesで期待する例外を確認し、正常な結果には具体的な期待値をassertしてください。

保存後、bash /opt/lab/checks/test-settings-isolation-lab/04-contract.shで確認してください。

欠落したときだけデフォルトを適用する(テスト)

/root/work/test-settings-isolation-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: load_settings(env)は、service=envのSERVICE(欠落時は'api')、debug=parse_bool(DEBUG、欠落時は'false')、port=parse_port(PORT、欠落時は'8000')、timeout=parse_timeout(TIMEOUT、欠落時は'5')、token=required_tokenの辞書です。空のSERVICEはValueErrorです。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。

getのデフォルト値と、or演算子によるデフォルト値への置き換えは、空文字列の扱いが異なります。実装ファイルは修正しません。pytest.raisesで期待する例外を確認し、正常な結果には具体的な期待値をassertしてください。

保存後、bash /opt/lab/checks/test-settings-isolation-lab/05-contract.shで確認してください。

公開する設定から機密値を除去する(テスト)

/root/work/test-settings-isolation-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: public_settings(settings)は、serviceとdebugだけを持つ新しい辞書です。元のデータは変更しません。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。

トークンの値の一部をマスクするよりも、フィールド自体を公開しない契約を使います。実装ファイルは修正しません。pytest.raisesで期待する例外を確認し、正常な結果には具体的な期待値をassertしてください。

保存後、bash /opt/lab/checks/test-settings-isolation-lab/06-contract.shで確認してください。

外部の変更と設定を分離する(テスト)

/root/work/test-settings-isolation-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: snapshot(env)は、load_settingsの結果を返します。呼び出した後でenvを変更しても、返された設定は変わりません。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。

アプリの起動時点の設定を、あとで変わる入力の辞書から切り離します。実装ファイルは修正しません。pytest.raisesで期待する例外を確認し、正常な結果には具体的な期待値をassertしてください。

保存後、bash /opt/lab/checks/test-settings-isolation-lab/07-contract.shで確認してください。

起動の失敗と公開レスポンスを確認する(テスト)

/root/work/test-settings-isolation-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: create_app(env)は、snapshotをただちに読み取り、誤った設定ならValueErrorでアプリの生成を失敗させます。GET /infoはpublic_settingsだけを返します。別のenvで作ったアプリ同士は、設定を共有しません。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。

サーバーが起動した後の最初のリクエストで設定エラーが発覚することがないように、生成時に検証します。実装ファイルは修正しません。pytest.raisesで期待する例外を確認し、正常な結果には具体的な期待値をassertしてください。

保存後、bash /opt/lab/checks/test-settings-isolation-lab/08-contract.shで確認してください。