テスト分割と並列実行
並列実行とテスト分割機能を使用すると以下を実現できます。
- CI/CD パイプラインのテストにかかる時間の削減
- テストを分割する Executor の数の指定
- CircleCI CLI が提供するオプション (名前やサイズに基づいて、またはタイミングデータを使って) によるテストスイートの分割
はじめに
パイプラインは、多くの場合コードがコミットされるたびに一連のテストが実行されるように設定されます。 プロジェクトに含まれるテストの数が多いほど、 1 つのコンピューティングリソースで完了するのに時間がかかるようになります。 この時間を短縮するために、複数の並列の実行環境でテストを分割し、実行することができます。 テスト分割は、CI/CD パイプラインのテスト部分を高速化できる優れた方法です。
テスト分割を使用すると、1 つのテストスイートにおいて分割する場所を以下の方法でインテリジェントに定義できます。
- 名前に基づいて
- サイズに基づいて
- タイミングデータを使用して
テスト分割のしくみ
例えば タイミングベース のテスト分割機能を使うと、 以前の テスト実行のタイミングデータを使って、並行で実行される指定した数のテスト環境でテストスイートをできるだけ均等に分割できます。 これにより、使用中のコンピューティング能力のテスト時間が可能な限り短くなります。
このタイミングデータを使ったテスト分割の効果を定量的に示すために、 CircleCI アプリケーションプロジェクト全体で実行されるテストスイートに parallelism: 10
を追加すると、実際にテスト時間が 26:11 から 3:55 に短縮されました。
タイミングベースのテスト分割により、テストを最も正確に分割でき、各テストスイートの実行を確実に最適化することができます。 分割する場所の決定には、必ず最新のタイミンングデータが使用されます。
go テストスイートを例にあげます。 この例では、すべてのテスト実行が 1つのテスト環境 (Docker コンテナ) で順番に実行されます。
jobs:
build:
docker:
- image: cimg/go:1.18.1
resource_class: large
working_directory: ~/my-app
steps:
- run: go test
以下の方法により、これらのテストをタイミングデータを使って分割できます。
- 並列実行により、多数の同一テスト環境 (下記例では 10 個) をスピンアップする
--split-by=timings
フラグを指定してcircleci tests split
コマンドを使用し、テストをすべての実行環境で均等に分割する
jobs:
build:
docker:
- image: cimg/go:1.18.1
parallelism: 10
resource_class: large
working_directory: ~/my-app
steps:
- run: go test -v $(go list ./... | circleci tests split --split-by=timings)
ジョブの並列実行レベルの指定
テストスイートは通常、.circleci/config.yml
ファイルの ジョブレベルで定義します。 parallelism
キーにより、ジョブを実行するためにセットアップする独立した Executor の数を指定します。
ジョブのステップを並列に実行するには、parallelism
キーに 2 以上の値を設定します。 下記の例では、parallelism
は 4
と設定されており、このジョブには 4 つの同一の実行環境がセットアップされることを意味します。 その後変更がなければ、ジョブ全体が各実行環境で実行されます。 各環境で別々のテストを実行して、テストの実行時間を短縮するには、 テスト分割の設定をする必要があります。
# ~/.circleci/config.yml
version: 2.1
jobs:
test:
docker:
- image: cimg/base:2022.11
auth:
username: mydockerhub-user
password: $DOCKERHUB_PASSWORD # context / project UI env-var reference
parallelism: 4
セルフホストランナーでの並列実行機能の使用
セルフホストランナーを使ったジョブでこの並列実行機能を使用するには、ジョブを実行するランナーリソースクラスに、少なくとも 2 つのセルフホストランナーが関連付けられていることを確認してください。 指定したリソースクラスでアクティブなセルフホストランナーの数より大きな並列実行の値を設定すると、実行するセルフホストランナーがない超過した並列タスクは、セルフホストランナーが使用可能になるまでキューに入ります。
詳細については、 CircleCI の設定 を参照してください。
CircleCI CLI を使用したテスト分割
CircleCI では、複数のコンテナに対してテストを自動的に割り当てることができます。 割り当ては、使用しているテストランナーの要件に応じて、ファイル名またはクラス名に基づいて行われます。 テスト分割には CircleCI CLI が必要で、実行時に自動的にビルドに挿入されます。
circleci tests
コマンド (glob
と split
) は、CircleCI コンテナ内にのみ存在する情報を必要とするため、CLI でローカル実行することはできません。CLI では、並列ジョブの実行時に複数の Executor にテストを分割できます。 それには、circleci tests split
コマンドでファイル名またはクラス名のリストをテストランナーに渡す必要があります。
セルフホストランナーは、CLI を使ってテストを分割する代わりに、circleci-agent
を直接呼び出すことができます。 これは、 タスクエージェントが既に $PATH
上に存在し、テスト分割時には追加の依存関係が削除されるからです。
1. テストファイルのグロブ
CLI では、以下のパターンを使用したテストファイルのグロブをサポートしています。
*
は、任意の文字シーケンスに一致します (パス区切り文字を除く)。**
は、任意の文字シーケンスに一致します (パス区切り文字を含む)。?
は、任意の 1 文字に一致します (パス区切り文字を除く)。[abc]
は、角かっこ内の任意の文字に一致します (パス区切り文字を除く)。{foo,bar,...}
は、中かっこ内のいずれかの文字シーケンスに一致します。
テストファイルをグロブするには、circleci tests glob
コマンドに 1 つ以上のパターンを渡します。
circleci tests glob "tests/unit/*.java" "tests/functional/*.java"
パターン照合の結果を確認するには、echo
コマンドを使用します。
# ~/.circleci/config.yml
version: 2
jobs:
test:
docker:
- image: circleci/<language>:<version TAG>
auth:
username: mydockerhub-user
password: $DOCKERHUB_PASSWORD # コンテキスト/プロジェクト UI 環境変数の参照
parallelism: 4
steps:
- run:
command: |
echo $(circleci tests glob "foo/**/*" "bar/**/*")
circleci tests glob "foo/**/*" "bar/**/*" | xargs -n 1 echo
2. テスト分割
a. タイミングデータに基づいた分割
store_test_results
を使用していない場合、テスト分割に使用できるタイミングデータがありません。一連の並列 Executor でテストスイートを最適化するための最良の方法は、タイミングデータを使用してテストを分割することです。 この方法では、テストが最も均等に分割され、テスト時間が短縮されます。
CircleCI は、テストスイートの実行が成功するたびに、 store_test_results
ステップでパスを指定しているディレクトリからタイミングデータを保存しています。 このタイミングデータには、ファイル名やクラス名ごとに各テストが完了するまでにかかった時間が含まれます。
タイミングで分割するには、分割タイプ timings
を付けて --split-by
フラグを使用します。 これで、使用可能なタイミングデータが分析され、テストが可能な限り均等に並列コンテナに分割されます。
circleci tests glob "**/*.go" | circleci tests split --split-by=timings
CLI は、テストスイートによって生成されたタイミングデータに、ファイル名とクラス名の両方が存在することを想定しています。 デフォルトでは、分割は --timings-type
で、出力は filename
に設定されています。 テストカバレッジ出力のフォーマットに応じて、異なるタイミングタイプを選択する必要がある場合があります。 有効なタイミングタイプは、filename
、classname
、testname
、autodetect
です。
cat my_java_test_classnames | circleci tests split --split-by=timings --timings-type=classname
部分的に検出されたテスト結果については、タイミングデータが見つからないテストにランダムな小さな値が割り当てられます。 このデフォルトの値は、--time-default
フラグを使って特定の値に上書きできます。
circleci tests glob "**/*.rb" | circleci tests split --split-by=timings --time-default=10s
手動でタイミングデータを格納および取得する場合は、 store_artifacts
ステップを使用します。
Error autodetecting timing type, falling back to weighting by name
というメッセージが出力されます。 この場合、テストはテスト名に基づきアルファベット順に分割されます。b. 名前に基づいた分割 (デフォルト)
デフォルトでは、--split-by
フラグを使用しない場合、circleci tests split
はファイル名またはクラス名の一覧が渡されることを想定しており、テスト名によってアルファベット順にテストを分割します。 ファイル名の一覧は、以下に挙げる複数の方法で用意できます。
- テストファイル名を含むテキストファイルを作成する
circleci tests split test_filenames.txt
- テストファイルへのパスを指定する
circleci tests split < /path/to/items/to/split
- テストファイルのグロブをパイプする
circleci tests glob "test/**/*.java" | circleci tests split
c. ファイルサイズに基づいた分割
ファイルパスを指定すれば、CLI はファイルサイズでも分割できます。 それには、分割タイプ filesize
を付けて --split-by
フラグを使用します。
circleci tests glob "**/*.go" | circleci tests split --split-by=filesize
3. 分割テストの実行
テストをグロブおよび分割しても、実際にテストが実行されるわけではありません。 テストのグループ化とテストの実行を結び付けるには、グループ化されたテストをファイルに保存してから、そのファイルをテストランナーに渡します。
circleci tests glob "test/**/*.rb" | circleci tests split > /tmp/tests-to-run
bundle exec rspec $(cat /tmp/tests-to-run)
ファイル /tmp/tests-to-run
の内容は、$CIRCLE_NODE_INDEX
と $CIRCLE_NODE_TOTAL
に応じて、コンテナごとに異なる値を持ちます。
手動による設定
CLI は現在のコンテナインデックスと使用可能な実行環境の数を調べます。 次に、決定論的な分割アルゴリズムを使用して、使用可能なすべてのコンテナでテストファイルを分割します。
デフォルトでは、プロジェクトの設定ファイルの parallelism
キーによってコンテナ数を指定します。 --total
フラグを使用すると、手動で設定できます。
circleci tests split --total=4 test_filenames.txt
同様に、現在のコンテナインデックスは $CIRCLE_NODE_INDEX
環境変数から自動的に決定されますが、--index
フラグを使用して手動で設定することもできます。
circleci tests split --index=0 test_filenames.txt
環境変数を使用したテスト分割
CircleCI には並列の Executor 間でのテスト分割処理を完全に制御するために環境変数が 2 つ用意されており、CLI の代わりに使用し、コンテナを個別に設定することができます。
$CIRCLE_NODE_TOTAL
はジョブの実行に使用されている並列コンテナの合計数、$CIRCLE_NODE_INDEX
は現在実行されている特定のコンテナのインデックスです。 詳細については、 プロジェクトの値と変数を参照してください。
その他のテスト分割方法
一部のサードパーティのアプリケーションやライブラリでも、テスト スイートの分割がサポートされていますが、 CircleCI ではこれらのアプリケーションの開発やサポートを行っていません。 CircleCI でこれらのアプリケーションを使用して問題が発生した場合は、オーナーに確認してください。 問題が解決しない場合は、 CircleCI の Discuss フォーラムで対処方法を検索するか、質問してみてください。
-
Knapsack Pro: 並列 CI ノード間でテストを動的に割り当て、テストスイートの実行を高速化します。 CI のビルド時間への効果は こちらのグラフでご確認ください。
- phpunit-finder:
phpunit.xml
ファイルに対してクエリを行い、テストファイル名の一覧を取得して出力するヘルパー CLI ツールです。 テストを分割して CI ツールのタイミングに基づいて並列に実行する場合に、このツールを使用すると便利です。 -
go list: Golang パッケージをグロブするには、組み込みの Go コマンド
go list ./...
を使用します。 これにより、パッケージテストを複数のコンテナに分割できます。go test -v $(go list ./... | circleci tests split)
既知の制限
現時点では、タイミングデータに基づいたテスト分割は Windows リソースクラスでは使用できません。
次のステップ
ドキュメントの改善にご協力ください
このガイドは、CircleCI の他のドキュメントと同様にオープンソースであり、 GitHub でご利用いただけます。 ご協力いただき、ありがとうございます。
- このページの編集をご提案ください (最初に「コントリビューションガイド」をご覧ください)。
- ドキュメントの問題点を報告する、またはフィードバックやコメントを送信するには、GitHub で issue を作成してください。
- CircleCI は、ユーザーの皆様の弊社プラットフォームにおけるエクスペリエンスを向上させる方法を常に模索しています。 フィードバックをお寄せいただける場合は、リサーチコミュニティにご参加ください。
サポートが必要ですか
CircleCI のサポートエンジニアによる、サービスに関する問題、請求およびアカウントについての質問への対応、設定の構築に関する問題解決のサポートを行っています。 サポートチケットを送信して、CircleCI のサポートエンジニアにお問い合わせください。日本語でお問い合わせいただけます。
または、 サポートサイト から、サポート記事やコミュニティフォーラム、トレーニングリソースをご覧いただけます。
CircleCI Documentation by CircleCI is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.