Skip to main content

欠陥のあるテストの検出と隔離

不安定なテストでは、コードに問題がない場合でもPRがブロックされます。一時的な障害が 1 回発生すると、ログを掘り下げ、再実行を押し、待機せざるを得なくなるため、マージが遅くなり、実際のリグレッションが見えなくなります

不安定なテストの検出

失敗したテストを自動的に再実行するように特定のビルドステップを設定します。再試行時にテストが成功すると、ビルドが緑色になり、テストに flaky というマークが付けられるので、後で修正できます。また、修正することもできます。 隔離 もしあれば エンタープライズ 計画。

失敗したテストを自動的に再実行できるようにする手順

これらのテストステップの入力により、失敗したテストの再試行回数を指定できます。実行中にテストが失敗すると、ステップは自動的にテストを再試行します。

2 回目の試行でテストに合格すると、テストは flaky とマークされます。ただし、Step 自体は成功するので、ビルドと PR チェックは緑色に変わります。

これにより、後で不安定なテストを特定して修正するために必要なデータを提供しながら、ブロックされることなく変更をマージできます。

たとえば、ネットワークリクエストがタイムアウトしたり、アニメーションが完了しなかったりして、UI テストが失敗することがあります。ビルド全体を失敗させる代わりに、失敗したテストを最大 3 回再試行するように構成できるようになりました。2 回目の試行で成功すると、PR のブロックが解除され、この特定のテストが不安定だったことがテストレポートでわかります

次のいずれかを使用する場合は、次の点に注意してください。 iOS デバイステスト ステップ 1 または Android 用仮想デバイステスト 手順を実行すると、失敗したテストだけでなく、テストスイート全体が再実行されます。

不安定なテスト検出の設定

以下のテストステップの入力フィールドで、不安定なテスト検出を設定します。

  1. iOS 用 Xcode テストビルドなしの Xcode テスト

    下の入力フィールドを設定 テスト繰り返し このステップでは:

    xcodetestforios.png
    • テスト繰り返しモード: リピートモードを定義します。たとえば、 retry_on_failure 失敗したテストのみを再実行します。

    • テストの最大繰り返し回数: テストを繰り返す最大回数。

    • 繰り返しのたびにテストを再起動: 繰り返しのたびにアプリを再起動するかどうかを制御します。

  2. iOS デバイステスト そして Android 向け仮想デバイステスト

    ステップで以下の入力フィールドを設定します。

    • テスト実行が再試行される回数: 失敗したテスト実行を再試行する回数を指定します。最初は失敗したが、再試行で成功した実行は「不安定である」と報告されます。最大値は 10 です。デフォルトは 0 (再実行なし)

      iosdevicetesting-flakytests.png

      でテストを実行する Android 向け仮想デバイステスト そして iOS デバイステスト 手順

      どちらのステップも、入力で設定されている回数だけ、常にすべてのテスト (成功と失敗) を再実行します。

  3. アンドロイドユニットテスト

    この機能は、から直接設定されます。 Gradle プラグイン。プロジェクト内で失敗したテストの再試行を有効にできます。 build.gradle ファイル。

    不安定なテストの隔離

    可能です 隔離します。 問題が修正されるまでビルドの実行を中断させる不安定なテスト。上記の手順では、隔離の詳細を指定すると、隔離されたテストが自動的にスキップされます

フレークテスト検出用の環境変数

テストステップが自動的に入力されるようになりました BITRISE_FLAKY_TEST_CASES 再試行が有効になっている場合、どの特定のテストで一貫性のない動作をしているかがわかります。

これらのステップのいずれかを再試行を有効にして実行すると、個々のテスト結果が自動的に追跡されます。

  • iOS 用 Xcode テスト (ID: xcode-test)

  • アンドロイドユニットテスト (ID: アンドロイドユニットテスト)

  • iOS デバイステスト (ID: iOS 用仮想デバイステスト)

  • Android向け仮想デバイステスト (ID: アンドロイド用仮想デバイステスト)

これらのステップは BITRISE_FLAKY_TEST_CASES 少なくとも 1 回は失敗したが、再試行中に少なくとも 1 回は合格したテストを含む Env Var です。

ザル BITRISE_FLAKY_TEST_CASES Env Var には、改行で区切られた不安定なテスト識別子のリストが含まれています。

- TestTarget_1.TestClass_1.TestMethod_1
- TestTarget_1.TestClass_1.TestMethod_2  
- TestTarget_1.TestClass_2.TestMethod_1
- TestTarget_2.TestClass_1.TestMethod_1

不安定なテスト Env Var をどのような場合に使用すべきかの例

この Env Var を使用すると便利なユースケースをいくつか紹介します。

  1. 不安定なテストが検出された場合、テストは失敗します。

    steps:
    ...
    - script:    
        run_if: '{{getenv "BITRISE_FLAKY_TEST_CASES" | ne ""}}'    
        inputs:    
         - content: |-        
         #!/bin/env bash        
         echo "Build failed due to flaky tests:"        
         echo "$BITRISE_FLAKY_TEST_CASES"       
         exit 1
  2. 欠陥のあるテストが検出された場合に Slack 通知を送信するには:

    steps:
    ...
    - slack:    
        run_if: '{{getenv "BITRISE_FLAKY_TEST_CASES" | ne ""}}'    
        inputs:    
        - webhook_url: $SLACK_WEBHOOK_URL    
        - text: |-        
            ⚠️ Flaky tests detected in build $BITRISE_BUILD_NUMBER:       
            $BITRISE_FLAKY_TEST_CASES
  3. GitHub の課題を自動的に作成します。

    steps:
    ...
    - script:    
        run_if: '{{getenv "BITRISE_FLAKY_TEST_CASES" | ne ""}}'    
        inputs:    
        - content: |-       
          #!/bin/env bash        
          ISSUE_BODY="Flaky tests detected in build 
    $BITRISE_BUILD_NUMBER:\n\n\`\`\`\n$BITRISE_FLAKY_TEST_CASES\n\`\`\`"        
          curl -X POST \          
           -H "Authorization: token $GITHUB_TOKEN" \          
           -H "Accept: application/vnd.github.v3+json" \         
           https://api.github.com/repos/owner/repo/issues \          
           -d "{\"title\":\"Flaky tests - Build $BITRISE_BUILD_NUMBER\",\"body\":\"$ISSUE_BODY\"}"

また、分析サービスにログインし、保存してアーティファクトを構築し、テストターゲットごとにカウントして分類し、ダウンストリームステップの環境変数を設定することもできます。

テスト結果と再試行後の結果

テストが失敗した場合、ステップは設定された制限までテストを再実行します。以前に失敗したテストが再試行で合格した場合、Bitrise では flaky とマークされます テスト タブ。再試行してもテストが失敗する場合、ステップは通常どおり失敗します

再試行後にすべてのテストに合格した場合、ビルドは成功し、不安定なテストは新しいテストレポートで強調表示されます。再試行後にいずれかのテストが失敗すると、そのステップは失敗し、ビルドステータスには FAILED と表示されます

失敗したテストケースと成功した実行を含む不安定なテストケース

テストケースの実行が成功または失敗した場合でも、テストケースは不安定とマークされます。ステータスの数と順序は関係ありません。たとえば、1 回の失敗、1 回の成功、1 回の失敗は、成功が 1 回、失敗が 2 回 (この順序で) 同様に「不具合」としてマークされます

不安定なテストの隔離

この機能により、テストスイート全体を中断することなく、問題のあるテストを一時的に切り分けることができます。マークを付けることができます。 不安定なテスト 隔離されたので、Bitriseは今後のビルドではそれらをスキップします。

隔離されたテストは別の場所で追跡されます テスト隔離 タブを押すと、隔離から削除するまでビルドからスキップされ続けます。

テスト隔離の使用は制限されています

この機能は以下でのみ利用可能であることに注意してください エンタープライズプラン。さらに、隔離されたテストへの完全な読み取り、書き込み、編集権限を持つことができるのは、管理者、プラットフォームエンジニア、開発者のみです。テスターと QA 担当者は読み取り専用です

次のような場合は、テストを隔離することをお勧めします。

  • テストはローカルでは合格しますが、CI では断続的に失敗します。

  • 欠陥のあるテストが見つかったが、すぐに修正する時間がない。

  • テストの安定性の問題を調査しながら、グリーンビルドを維持したい。

  • 既知の問題のあるテストでは、自動再実行によってビルド時間が消費されないようにする必要があります。

検疫所へのテストの追加

からテストを隔離できます テスト [タブ] または [から] インサイト 予期しないビルド失敗を防ぐには:

[テスト] タブから

  1. に移動します 薄片状 の「」タブ テスト タブ。

  2. クリック 隔離リストに追加 不安定なテストの隣に

  3. をクリックして隔離範囲 (ワークフロー、パイプライン、またはブランチ) を定義します 検疫範囲を編集

  4. 後で参照できるように、オプションのコメントを追加してください。

pipelinebuild-quarantine.png
editscope-quarantine.png

インサイトから

  1. に移動 インサイトテスト不安定なテストテストケース

  2. を適用する プロジェクトフィルター

  3. クリック 隔離リストに追加 ブレークダウンビュー内の任意のテストの横にあります。

  4. をクリックして検疫範囲と条件を設定します 検疫範囲を編集

  5. 送信すると、そのテストを今後の実行から除外できます。

insights-quarantine.png

隔離範囲の定義

デフォルトでは、特に指定しない限り、隔離はプロジェクトのすべてのビルドに適用されます。すべてのブランチとすべてのターゲット (ワークフローとパイプライン) でのテストをスキップするには、フィールドを空白のままにしておきます

ブランチとターゲットによって範囲を制限できます。

  • ブランチ (オプション): ワイルドカード対応 (*) を使用してスペースで区切ったブランチパターンを入力します。 mainrelease-*feature/auth-*

  • ターゲット (オプション): ワークフローとパイプラインの名前をスペースで区切って入力します。例: cinightly-testsrelease-pipeline

ブランチとターゲットを組み合わせて範囲を狭める

ブランチとターゲットの両方を設定した場合、テストはブランチとターゲットの両方が一致する場合にのみ隔離されます。サイレンシングしすぎないように、パターンは厳密にしてください。優先 release-* オーバー * そして、不安定なテストを実行する特定のワークフローをターゲットにします。

次のものを使用できます [コメント] テストを隔離した理由を思い出すことができるように、隔離理由を書き留めるフィールドとチケットリンクを記載してください。

スコープは後で検疫ダッシュボードから編集または削除できます。

隔離されたテストの管理

から検疫ダッシュボードにアクセスします テスト:隔離 CI ホームページのタブ。

隔離テストへのアクセス

隔離されたテストへの完全な読み取り、書き込み、編集権限を持つことができるのは、管理者、プラットフォームエンジニア、開発者のみであることに注意してください。テスターと QA 担当者は読み取り専用です

ダッシュボードには以下が表示されます。

  • テスト名とモジュールパス。

  • テストが隔離されたビルド。

  • タイムスタンプとそれを隔離したユーザー。

  • 各テストのインサイトへの直接リンク。

managingquarantinedtests.png

ビルドで実行されなかった隔離されたテストは、ビルドには表示されません テスト タブ。隔離されたテストは、検疫から削除するまでダッシュボードに残ります。

対象範囲を狭め、不安定な状態が発生するブランチ/ワークフローを隔離することをおすすめします。また、今後のクリーンアップのためにコメントを残し、長期にわたる隔離や修正テストについては定期的にレビューを行うことも役立ちます

隔離テスト用の環境変数

ビルド中、Bitriseは隔離されたテストのスコープリストを以下に注入します $BITRISE_QUARANTINED_TESTS_JSON 環境変数。サポートされているステップは、この変数を読み取ってそれらのテストをスキップし、自動再実行から除外します。カスタムステップやランナーへの直接呼び出しを使用する場合は、 $BITRISE_QUARANTINED_TESTS_JSON そして、それに応じてテストランナーの引数をフィルタリングしてください。Env Var の値の例を以下に示します

BITRISE_QUARANTINED_TESTS_JSON = "[
  {
     testCaseName: 'EnableQuarantiningDisabledByPlan',
     testSuiteName: ['UITests', 'CI', 'Settings'],
     className: 'BuildSettings'
  },
  {
     testCaseName: 'EnableQuarantiningDisabledByLDFlag',
     testSuiteName: ['UITests', 'CI', 'Settings'],
     className: 'BuildSettings'
  }
  ...
]"

隔離テストをサポートする手順

次の手順では、指定された範囲内の隔離されたテストが自動的にスキップされます。カスタムステップを使用するか、ランナーを直接呼び出す場合は $BITRISE_QUARANTINED_TESTS_JSON それに応じてテストランナーの引数をフィルタリングしてください。

カスタムステップの検疫サポート

他のステップで変換できます BITRISE_QUARANTINED_TESTS_JSON テストランナーが特定のテストをスキップできるように、適切な形式のデータ。たとえば、fastlane を設定できます。 scan 隔離されたテストをスキップするためのアクションまたは Gradle Init スクリプト。

以下にいくつか例を挙げます。

例1 Fastlane-スキャンアクションを実行中

Bitriseのワークフローでは、公式のBitriseを使ってファストレーンレーンを実行することができます。 ファストレーン ステップ 1 または a を使用する スクリプト ステップ。

いずれのシナリオでも、最初に JSON データをカンマで区切られたリストに変換する必要があります。 TestTarget/TestClass/TestMethod アイテム。

JSON データは、設定 YAML ファイルまたは fastlane 設定ファイル () のいずれかで変換できます。Fastfile)。

コンフィギュレーション YAML

ファストファイル

  1. この例では、 スクリプト ステップは、隔離されたテスト JSON データをカンマで区切られたリストに変換します。 TestTarget/TestClass/TestMethod に項目を追加してリストを公開します。 BITRISE_QUARANTINED_TESTS_LIST 環境変数:

    workflows:
      test:
        steps:
        ...
        - script:
            title: Convert BITRISE_QUARANTINED_TESTS_JSON
            inputs:
            - content: |-
                #!/usr/bin/env bash
                set -e
    
                # Read JSON from environment variable
                json="$BITRISE_QUARANTINED_TESTS_JSON"
    
                # Convert to array of TestTarget/TestClass/TestMethod
                test_array=($(echo "$json" | jq -r '.[] | "\(.testSuiteName[0])/\(.className)/\(.testCaseName)"'))
    
                # Join the array elements with a comma
                skip_testing_list=$(IFS=, ; echo "${test_array[*]}")
    
                # Export the comma-separated string as an environment variable to be used by following Fastlane Steps
                envman add --key BITRISE_QUARANTINED_TESTS_LIST --value "$skip_testing_list"
        - fastlane:
            inputs:
            - lane: test
        ...
  2. ザ・ BITRISE_QUARANTINED_TESTS_LIST その後、変数を使用して以下を設定できます。 skip testing のオプション scan ファストレーンでのアクション:

    lane :test do
      ...
      scan(skip_testing: ENV['BITRISE_QUARANTINED_TESTS_LIST'].split(','))
      ...
    end
  • Fastfile のデータを変換するには、次のものも必要です。 BITRISE_QUARANTINED_TESTS_JSON 変数。以下の例では、fastlane レーンは隔離されたテストのリストを解析してテスト識別子にし skip_testing:

    lane :test do
      ...
      skip_testing = JSON.parse(ENV['BITRISE_QUARANTINED_TESTS_JSON']).map do |item|
        "#{item['testSuiteName'][0]}/#{item['className']}/#{item['testCaseName']}"
      end unless ENV['BITRISE_QUARANTINED_TESTS_JSON'].to_s.empty?
      scan(skip_testing: skip_testing)
      ...
    end

例2 スクリプトステップ-xcodebuild テストまたはビルドせずにテストを実行する

この例はを変換します BITRISE_QUARANTINED_TESTS_JSON 変数の値は skip-testing xcodebuild のオプション test または test-without-building コマンド。

workflows:
  test:
    steps:
    - script:
        title: Convert BITRISE_QUARANTINED_TESTS_JSON
        inputs:
        - content: |-
            #!/usr/bin/env bash
            set -e

            # Read JSON from environment variable
            json="$BITRISE_QUARANTINED_TESTS_JSON"

            # Convert to array of -skip-testing:TestTarget/TestClass/TestMethod
            test_array=($(echo "$json" | jq -r '.[] | "-skip-testing:\(.testSuiteName[0])/\(.className)/\(.testCaseName)"'))

            # Join the array elements with a space
            skip_testing_list="${test_array[*]}"
            
            # Export the space-separated string as an environment variable to be used by following Fastlane Steps
            envman add --key XCODEBUILD_SKIP_TESTING_OPTION --value "$skip_testing_list"
    - script:
        inputs:
        - content: xcodebuild test -scheme <SCHEME> -destination <DESTINATION> $XCODEBUILD_SKIP_TESTING_OPTION

例3 スクリプトステップ-Gradle ユニットテストの実行

Gradle を使用して Android ローカルユニットテストを実行する場合 (/gradlew testUnitTest)、Gradle Init スクリプトを使用すると、隔離されたテストケースをスキップできます。

workflows:
  test:
    steps:
    - script:
        title: Create skip testing init script
        inputs:
        - content: |-
            #!/bin/bash

            # Script to generate Gradle init script for quarantined tests
            # Reads BITRISE_QUARANTINED_TESTS_JSON environment variable and generates
            # a Gradle init script that excludes the specified tests

            set -ex

            # Check if BITRISE_QUARANTINED_TESTS_JSON is set
            if [[ -z "${BITRISE_QUARANTINED_TESTS_JSON:-}" ]]; then
                echo "Error: BITRISE_QUARANTINED_TESTS_JSON environment variable is not set" >&2
                exit 1
            fi

            # Parse JSON and extract test patterns
            # Each test will be excluded using the pattern: className.testCaseName
            excluded_tests=$(echo "$BITRISE_QUARANTINED_TESTS_JSON" | jq -r '.[] | .className + "." + .testCaseName')

            # Generate the Gradle init script
            init_script_file=./gradle_quarantine_init.gradle.kts
            cat << 'EOF' > "$init_script_file"
            allprojects {
                tasks.withType<Test>().configureEach {
            EOF

            # Add filter.excludeTestsMatching lines for each excluded test
            while IFS= read -r test_pattern; do
                if [[ -n "$test_pattern" ]]; then
                    echo "        filter.excludeTestsMatching(\"$test_pattern\")" >> "$init_script_file"
                fi
            done <<< "$excluded_tests"

            cat << 'EOF' >> "$init_script_file"
                }
            }
            EOF

            # Output the path to the generated init script file
            envman add --key GRADLE_INIT_SCRIPT_FILE --value "$init_script_file"
    - script:
        title: Run tests
        inputs:
        - content: |-
            #!/bin/bash
            set -ex
            ./gradlew :app:testDebugUnitTest "--init-script" "$GRADLE_INIT_SCRIPT_FILE"

ザ・ Create skip testing init script ステップは次の Gradle init スクリプトを作成します。

allprojects {
    tasks.withType<Test>().configureEach {
        filter.excludeTestsMatching("<package_name>.<test_class>.<test_method_1>")
        filter.excludeTestsMatching("<package_name>.<test_class>.<test_method_2>")
        ...
    }
}