不安定なテスト: 問題の原因とそれを永久に修正する方法

  1. ホーム
  2. ブログ
  3. テストの自動化

テストの自動化

不安定なテスト: 問題の原因と問題を永久に修正する方法

ランダムに失敗するテストは、チームに失敗を無視するよう教えます。実際に何が機能するかを示すデモを使用して、テストが不安定であることを確認し、本当の原因を追跡し、修正する方法を学びます。

読了目安9分
不安定なテスト: 問題の原因とそれを永久に修正する方法

ポイント

  • 不安定なテストは、同じコードで合格したり不合格になったりしますが、変更はありません。
  • 通常の原因は、タイミング、共有状態、不安定なデータ、外部サービス、環境の違いです。
  • 固定スリープを特定の条件の待機に置き換え、すべてのテストに独自のデータを与えます。
  • Retries can hide real bugs.不安定なテストを隔離し、期限内に修正するか、削除します。

ビルドが赤くなり、誰かが「再実行」をクリックするとビルドは成功し、全員が先に進みます。無害に見えますが、不安定なテストは、テスト スイートが持つ最も価値のあるもの、つまりテスト スイートに対する人々の信頼をゆっくりと破壊します。通常、障害はノイズですが、実際のバグも紛れ込みます。

このガイドでは、テストが不安定になる理由と、それぞれの原因を適切に修正する方法について説明します。 Python 3.13 でタイミングと共有状態のデモを実行し、表示された数値はそれらの実行から得られました。これらは不安定さをわかりやすくするために設計された小規模なデモンストレーションであるため、ご自身の失敗率は異なります。

不安定なテストとは何ですか?

不安定なテストでは、同じコードに対して異なる結果が得られます。製品に何も変更がなかったとしても、ある実行では成功し、次の実行では失敗します。テストに問題があるか、コードに実際の断続的なバグが含まれている可能性があります。この 2 番目の可能性こそが、単純にそれらを無視すべきではない理由です。

不安定なテストはなぜ非常にコストがかかるのか

  • 信頼の喪失。 人々は、赤が「おそらく不安定」であることを意味すると考えると、実際の失敗を調査するのをやめます。
  • 時間の無駄。 開発者はパイプラインの再実行と、変更とは関係のない障害の調査に何時間も費やします。
  • 配信が遅くなります。再実行時にマージと待機が解除され、CI/CDのポイントが損なわれます。
  • 隠れたバグ。 製品内の競合状態はテスト ノイズのように見える場合があり、これらは実稼働環境に害を及ぼすものです。

Common causes at a glance

原因 典型的な兆候 通常の修正
タイミングと非同期 Fails more when CI is busy 条件を待つ
共有状態 Fails depending on test order データの分離、状態のリセット
日付とランダム性 Fails at certain times or days クロックとシードを制御する
脆性セレクター UI変更後に失敗する 安定したロケーターを使用する
外部サービス 依存関係が遅い場合に失敗する 嘲笑するかスタブにする
環境の違い ローカルでは成功するが、CI では失敗する コンテナと揃える
リソース制限 並列実行では失敗する ワーカーを調整し、競合を修正する

原因 1: スリープのタイミングと固定

これが最も一般的な原因です。テストでは、バックグラウンド作業が一定の遅延後に完了すると想定しています。効果を確認するために、作業に最大 2 ミリ秒かかる小規模なテストを作成しましたが、テストはチェックするまでにわずか 1 ミリ秒待機します。

def flaky_timing():
    result = {}
    def work():
        time.sleep(random.uniform(0, 0.002))
        result["done"] = True
    threading.Thread(target=work).start()
    time.sleep(0.001)        # hope it has finished
    assert result.get("done")

修正するには、上限を十分に設定して実際の状態を待つことです。

def fixed_timing():
    result = threading.Event()
    def work():
        time.sleep(random.uniform(0, 0.002))
        result.set()
    threading.Thread(target=work).start()
    assert result.wait(timeout=1)   # continues as soon as it is ready

各バージョンを 200 回実行しました。

バージョン 200 件中失敗
固定睡眠 102
条件を待つ 0

その結果、単なる信頼性以上のものが得られます。修正バージョンは作業が完了するとすぐに処理が進むため、通常は高速化されます。スリープを長くすると、最初のバージョンが失敗する頻度は減りますが、毎回の実行が遅くなり、ビジー状態の CI マシンでは依然として失敗する可能性があります。

ブラウザテストでも同じ原則が当てはまります。 time.sleep の代わりに、Playwright の自動待機、または明示的な Selenium 待機 を使用します。

# Playwright: retries until true or a timeout is reached
expect(page.get_by_text("Order placed")).to_be_visible()

# Selenium: an explicit wait for a condition
WebDriverWait(driver, 10).until(
    EC.visibility_of_element_located((By.ID, "confirmation"))
)

原因 2: 状態とテスト順序の共有

テストがデータを共有する場合、一方が他方に影響を与える可能性があり、結果は実行順序によって異なります。これら 2 つのテストは 1 つの共有リストを使用します。

store = []

def test_add():
    store.append("x")
    assert len(store) == 1

def test_empty():
    assert store == []
注文 test_add test_empty
空にしてから追加します パス パス
追加してから空にする パス 失敗

私たちの実行によりこれが確認されました。これが、スイートが何か月も経過しても、誰かが並列実行やランダムな順序のプラグインを追加すると壊れる可能性がある理由です。治療法は隔離です。

  • テストをセットアップしてクリーンアップするフィクスチャを使用して、各テストに新しいデータを与えます。 pytest チュートリアル ではその方法を示しています。
  • 共有の「テスト ユーザー」の代わりに、ランダムなサフィックスが付いた電子メールなどの一意のレコードを作成します。
  • 設定で許可されている場合は、データベース テストを トランザクション でラップし、ロールバックします。
  • これらの問題が早期に現れるように、定期的にテストをランダムな順序で実行してください。

原因 3: 日付、タイムゾーン、ランダム性

1 週間を通して合格したテストでも、土曜日、深夜、または月の最終日に不合格になる可能性があります。ロジック内の時計を読み取るのではなく、時間を入力にします。

def is_weekend(today=None):
    today = today or datetime.date.today()
    return today.weekday() >= 5

is_weekend(datetime.date(2026, 11, 7))   # True  (a Saturday)
is_weekend(datetime.date(2026, 11, 9))   # False (a Monday)

これらのテストでは、いつでも同じ答えが得られます。同様に、テストでランダムな値に固定シードを設定し、ランダム データを使用するときにそれをログに記録して、障害を再現できるようにします。セット、外部ソースからの辞書、または ORDER BY のないデータベース クエリからの特定の順序を想定しないでください。

原因 4: セレクターが壊れやすい

div:nth-child(3) > span のボタンを見つけるテストは、レイアウトを微調整すると中断され、一部の画面サイズまたはバリアントでのみ失敗する可能性があります。ユーザーに表示される内容、または専用のテスト属性に基づいてロケーターを優先します。 Playwright locators のガイドでは、それらを試す順序について説明しています。アニメーションもよくあるトラブルの原因であるため、テスト環境ではアニメーションを無効にすることを検討してください。

原因 5: 外部サービスとネットワーク

テストで実際の決済プロバイダーまたはサードパーティ API を呼び出す場合、その結果は他のユーザーの稼働時間とレート制限に依存します。ほとんどのテストではそれらの呼び出しをモックし、実際のサービスに対して少数の個別のコントラクト チェックを保持して、変更があった場合に学習できるようにします。私たちのガイド単体テストと統合テストこれらを分割する方法を説明します。

ヒント: 「ローカルでは成功、CI では失敗」は通常、速度、タイムゾーン、リソース、またはバージョンの違いを意味します。同じコンテナ イメージでローカルおよび CI でテストを実行すると、これらの多くが削除されます。 Docker Compose チュートリアルが役に立ちます。

不安定なテストを診断する方法

  1. 確認します。 変更せずにテストを何度も実行します。 pytest では、単純なシェル ループが機能し、pytest-repeat などのプラグインを使用すると --count=100 が得られます。
  2. 証拠を記録します。 失敗ごとにログ、スクリーンショット、トレースを保存します。 Playwright のトレース ビューアはここで特に役立ちます。
  3. 条件を重視します。 限られた CPU で並列またはランダムな順序で実行します。失敗を多くすることで勉強しやすくなります。
  4. 単独でテストします。 常に単独で成功するが、スイートでは失敗する場合は、共有状態が疑われます。
  5. パターンを探します。どのマシン、時刻、近隣のテストが関係しているかを確認します。
  6. 製品を確認してください。これがアプリケーションで実際の競合状態である可能性があるかどうかを正直に尋ねてください。

自動再試行は修正ですか?

再試行は短期的なセーフティネットとしては役立ちますが、治療法ではありません。 CI で 1 回の自動再試行を行うと、調査中に作業を続行できますが、合格するために再試行が必要なテストでも何かがわかります。さらに悪いことに、20% の確率で失敗する実際の断続的な製品のバグは、通常は気付かれずに再試行されてしまいます。

次のルールで再試行を使用します。再試行を 1 つまたは 2 つに制限し、再試行が必要なすべてのテストを記録し、そのリストを To Do リストとして扱います。 「再試行で合格」と報告されたテストは、成功ではなく、調査すべき問題としてカウントされる必要があります。

不安定なテストのためのチームプロセス

  1. 測定。 どのテストが失敗し、同じコミットで成功するかを追跡します。多くの CI ツールやテスト レポーターでこれを確認できます。
  2. 隔離。 確認済みの不安定なテストをブロッキング実行から移動します。これにより、全員の混乱を防ぐことができますが、実行と追跡は続けてください。
  3. 所有者と期限を割り当てます。 期限のない隔離は単なる墓場となります。
  4. 修正または削除。 テストを修正する価値がない場合は、削除し、そのカバレッジが他の場所にまだ存在することを確認します。
  5. 防止。 マージ前に新しいテストを数回実行します。 50 回の実行に 1 回失敗するテストは、6 か月かかるよりも今の方がうまく検出できます。

不安定なテストをバックグラウンド ノイズとしてではなく、優先的にバグとして扱います。 重大度と優先度で説明されているように、重大度と優先度はここにも適用されます。脆弱性が蔓延している場合は、より広範なテスト自動化の間違いについて読み、ブラウザ レベルでテストが多すぎるかどうかを検討してください。

まとめ

不安定なテストはランダムに感じられますが、実際にはランダムであることはほとんどありません。ほぼすべての人が、時間、順序、データ、環境についての仮定にたどり着きますが、それは必ずしも真実ではありません。今週パイプラインで最も頻繁に失敗するテストを選択してください。 50 回実行して仮定を見つけ、それを永久に修正します。 1 つの実際の修正は、チームに 100 回以上の再実行を教えることになります。

よくある質問

不安定なテストとは何ですか?

不安定なテストは、コードを変更しなくても、一部の実行では合格し、他の実行では失敗します。原因はテスト、環境、または製品にある可能性があります。

不安定なテストの原因は何ですか?

最も一般的な原因は、タイミングの仮定、テスト間の共有状態、不安定なセレクター、外部サービス、日付の処理、CI とローカル マシン間の違いです。

不安定なテストは削除する必要がありますか?

まずそれらを修正してみてください。テストの価値が低く、修正にコストがかかる場合は、削除しても問題ありませんが、その重要なカバレッジが別の場所に存在することを確認してください。

失敗したテストを自動的に再試行する必要がありますか?

限定的なセーフティ ネットとしては可能ですが、各再試行を記録して調査してください。再試行だけでは問題が隠れ、実際の断続的なバグが運用環境に到達する可能性があります。

不安定なテストを見つけるにはどうすればよいですか?

CI 履歴またはレポート ツールを使用して、同じコミットで失敗したテストと成功したテストを探します。疑わしいテストをループ内で何度も実行することもできます。

学び続ける

Stack Quoraと連携する

テスト、WordPress、または SEO についてサポートが必要ですか?

私たちは、チームがより優れたソフトウェアを出荷し、サイトを成長させるのを支援します。あなたが取り組んでいることを教えてください。明確な次のステップを提案します。

読み続けてください

詳しいガイドはこちら テスト、AI、最新の開発

この記事は気に入りましたか?トピックを選択して、最新のチュートリアルに直接アクセスしてください。

人工知能

AI 幻覚の説明: 言語モデルが物事をでっち上げる理由とそれを軽減する方法

AI 幻覚について説明しました。幻覚とは何か、言語モデルが幻覚を生成する理由、一般的な種類、グラウンディングとプロンプトで幻覚を軽減する方法、そして私たちが実行した簡単なチェックです。

記事を読む →
DevOps

環境変数の説明: .env ファイル、シークレット、および設定が正しく行われている

実行した Python の例を使用して環境変数について説明します。環境変数の読み取りと設定、すべての値が文字列である理由、.env ファイル、プロセスの継承、シークレット、検証について説明します。

記事を読む →
ソフトウェア開発

API レート制限の説明: アルゴリズム、ヘッダー、および 429 エラーの処理

API レート制限については、実行したシミュレーションで説明します。トークン バケット、固定ウィンドウ、スライディング ウィンドウ、429 応答、レート制限ヘッダー、バックオフによる再試行、およびテスト制限です。

記事を読む →
テスト自動化

Python でのモッキング:unittest.mock、patch、およびテストを嘘にする間違い

Python でのunittest.mockによるモック、テスト済み: モックとreturn_value、side_effect、名前が使用されている場所のパッチ、仕様と自動仕様、日付のモック、およびよくある間違い。

記事を読む →
ソフトウェアテスト

コードカバレッジの説明: 回線カバレッジとブランチカバレッジ、および 100% の実際の意味

実際のcoverage.pyの実行でコードカバレッジを説明: 行、分岐、条件のカバレッジ、レポートの読み取り、100%のカバレッジでもバグを見逃す可能性がある理由、および賢明なターゲット。

記事を読む →
データベースとSQL

SQL における DELETE、TRUNCATE、DROP: 違い、ロールバック、およびそれぞれをいつ使用するか

DELETE、TRUNCATE、DROP について、テスト済み SQL を使用して説明します。それぞれの削除内容、ロールバック、トリガー、ID リセット、速度、PostgreSQL、MySQL、SQL Server、SQLite の違いについて説明します。

記事を読む →
データベースとSQL

SQL ビューの説明: 作成、使用、落とし穴の回避、例を示します

SQL ビューについて、実行したクエリとともに説明しました。ビューの作成、ライブ結果、列の非表示、パフォーマンス、更新可能なビューと具体化されたビュー、壊れた依存関係、ベスト プラクティスについて説明します。

記事を読む →
ソフトウェア開発

TypeScript インターフェイスと型: 実際の違い、コンパイラでテスト

TypeScript インターフェイスと型、実際のコンパイラでチェック: 両方でできること、片方だけでできること、宣言のマージ、拡張と交差、および従うべき簡単なルール。

記事を読む →
ソフトウェア開発

Python *args と **kwargs の説明: 柔軟な関数と明確な例

Python の *args と **kwargs について、実行したコードで説明しました。パッキングとアンパッキング、パラメーターの順序、キーワードのみの引数と位置のみの引数、デコレーター、一般的なエラーです。

記事を読む →
ソフトウェア開発

Python Lambda 関数: 構文、実際の用途、および使用すべきでない場合

Python のラムダ関数について、実行したコードとともに説明します。構文、キーによるソート、マップとフィルター、クロージャー、遅延バインディング トラップ、通常の def がより適切な選択である場合。

記事を読む →
人工知能

AIエージェントとは何ですか?シンプルなエージェント ループを実行できる仕組み

AIエージェントとは何ですか?エージェント ループ、ツール、メモリ、ガードレール、実行した小さな Python ループ、実際のリスク、およびシンプルなチャットボットがより良い選択である場合についてのわかりやすいガイドです。

記事を読む →
DevOps

モニタリングと可観測性: ログ、メトリクス、トレースによる本当の違い

モニタリングと可観測性について説明: 既知および未知の障害、ログ、メトリクスとトレース、チェックアウト インシデントのウォークスルー、ゴールデン シグナル、OpenTelemetry、および便利なアラート。

記事を読む →
DevOps

Docker ボリュームとバインド マウント: コンテナ データを正しい方法で保持する

Docker ボリュームとバインド マウントについて説明します。コンテナ データが消える理由、名前付きボリューム、バインド マウント、tmpfs、Compose の例、バックアップ、アクセス許可、およびそれぞれをいつ使用するかについて説明します。

記事を読む →
テスト自動化

TDD と BDD: 主な違い、1 つの機能が両方の方法で構築され、それぞれをいつ使用するか

TDD と BDD は、両方の方法で構築された 1 つの割引機能で説明されています。実行したコードによるレッド-グリーン-リファクタリング、Given-When-Then シナリオ、ツール、トレードオフ、およびそれぞれをいつ使用するかです。

記事を読む →
ソフトウェアテスト

ユーザー受け入れテスト (UAT): プロセス、チェックリスト、および実際の例

ユーザー受け入れテストの説明: 誰が実行するか、QA との違い、7 段階のプロセス、UAT テスト ケース テンプレート、チェックリスト、サインオフ基準、およびよくある間違い。

記事を読む →
データベースとSQL

SQL NULL 値の説明: IS NULL、COALESCE、NULLIF、および回避すべきトラップ

SQL の NULL 値について、実行したクエリで説明しました。3 つの値のロジック、IS NULL、COALESCE、NULLIF、集計、結合、並べ替えでの NULL の扱い方、およびそれが密かに引き起こすバグです。

記事を読む →
データベースとSQL

SQL CASE WHEN ステートメント: 構文、実際の例、およびよくある間違い

SQL CASE WHEN では、単純な検索フォーム、条件順序、SUM を使用したピボット、COUNT トリック、カスタム並べ替え、更新、回避すべき NULL トラップなど、実行したクエリを使用して説明しました。

記事を読む →
ソフトウェア開発

JWTとは何ですか? JSON Web トークンを実際の例で説明する

JWTとは何ですか? JSON Web トークンについて説明しました。3 つの部分、実行したコード内で構築および改ざんされたトークン、JWT とセッション、ストレージ、リフレッシュ トークン、およびセキュリティ上の間違いです。

記事を読む →
ソフトウェア開発

JavaScript イベントループの説明: コールスタック、マイクロタスク、タイマーと例

JavaScript イベント ループについて、実行したコードで説明しました。コール スタック、タスク キュー、マイクロタスク キュー、setTimeout 0 が即時ではない理由、ブロッキング、スターベーション、およびインタビュー クイズです。

記事を読む →
ソフトウェア開発

Python ジェネレーターと収量の説明: 実際の例でメモリを節約する

Python ジェネレーターと Yield について、実行したコードを使用して説明します。一時停止と再開の方法、メモリ節約の測定、パイプライン、無限シーケンス、Yield from、send、および一般的なトラップについて説明します。

記事を読む →
人工知能

埋め込みとは何ですか?ベクトルとコサインの類似度を簡単に説明

埋め込みとは何ですか?ベクトル、コサイン類似度、セマンティック検索に関する平易なガイド。私たちが実行した小さな例、一般的な使用法、モデルの選択、既知の制限が含まれています。

記事を読む →
DevOps

Blue-Green デプロイメントと Canary デプロイメント: 違い、トレードオフ、および選択方法

ブルー グリーン デプロイメントとカナリア デプロイメントについて説明: それぞれの仕組み、コスト、リスクとロールバック、テストした安定したユーザー バケット化、データベース移行、ローリング アップデート、および選択方法。

記事を読む →
テスト自動化

不安定なテスト: 問題の原因とそれを永久に修正する方法

不安定なテストについては、私たちが実行したデモを使用して説明しました。主な原因、確認および診断する方法、タイミングの適切な修正、共有された状態と日付、隔離プロセスなどです。

記事を読む →
ソフトウェアテスト

負荷テストとストレス テスト: 違い、メトリクス、および k6 の例

ロード テストとストレス テストについて説明します。それぞれが何を証明するか、スパイク テストとソーク テスト、重要な指標、平均値が誤解を招く理由、K6 の例、テストの計画方法について説明します。

記事を読む →
ソフトウェアテスト

テスト ケースとテスト シナリオ: 主な違い、例、およびそれぞれをいつ使用するか

テスト ケースとテスト シナリオの説明: 明確な定義、並べて表、シナリオからケースへの 1 つのパスワード リセット機能、テンプレート、およびそれぞれをいつ使用するか。

記事を読む →
データベースとSQL

SQL クエリの最適化: 実行プランを読み取り、タイミングを考慮して遅いクエリを修正

SQL クエリの最適化は 100 万行でテストされています。実行プランの読み取り、適切なインデックスの追加、検索対象外のフィルターの修正、複合インデックスとキーセットのページネーションの使用。

記事を読む →
データベースとSQL

SQL UNION と UNION ALL: 違い、例、テストされたパフォーマンス

SQL UNION と UNION ALL について、実行したクエリで説明しました。重複、両方に従うルール、実際のデータのバグ、測定されたパフォーマンス、INTERSECT と EXCEPT、およびそれぞれをいつ使用するかについて説明します。

記事を読む →
ソフトウェア開発

JavaScript Fetch API チュートリアル: GET、POST、エラー処理、タイムアウト

ローカル サーバーに対してテストされた JavaScript Fetch API チュートリアル: GET と POST、404 がスローされない理由、再利用可能なヘルパー、クエリ文字列、タイムアウト、CORS、およびよくある間違い。

記事を読む →
ソフトウェア開発

Python ファイル処理: 読み取り、書き込み、CSV、JSON、パスライブラリと例

Python のファイル処理について、実行したコードで説明します。オープン モード、大きいファイルの読み取り、CSV、JSON、パスライブラリ、バイナリ ファイル、安全な書き込み、エラー処理、およびよくある間違いについて説明します。

記事を読む →
ソフトウェア開発

Git mergeとrebaseの違い:例と使い分け

Git mergeとrebaseの違いを、実際のログ出力、比較表、rebaseの黄金律、安全なチームワークフローで解説します。

記事を読む →
ソフトウェア開発

Pythonのリスト内包表記:構文・例・使うべきでない場面

Pythonのリスト内包表記を解説:構文、フィルタ、if-else、ネストしたループ、実測した処理時間、そして通常のforループの方が良い場面。

記事を読む →
データベースとSQL

SQL JOINを例で解説(Inner・Left・Right・Full)

サンプルのテーブルとクエリでSQL JOINを順に解説。INNER、LEFT、RIGHT、FULL、CROSS、SELFに加え、よくある間違いとパフォーマンスのコツも紹介します。

記事を読む →