コンテンツへスキップ

Gradle テストシャーディング

Tuist Gradle プラグインには、テストシャーディングの組み込みサポートが含まれています。プロジェクトのテストタスクの以前の CI 実行で Tuist が記録したテストスイートを計画し、Tuist サーバーを使用して履歴のタイミングデータに基づいてバランスの取れたシャードプランを作成します。

REQUIREMENTS

仕組み#

テストシャーディングは 2 つのフェーズからなるワークフローに従います:

  1. ビルドフェーズ: Tuist は、プロジェクト用に記録されたテストスイートからサーバー上で シャードプラン を作成します。サーバーは過去 30 日間の履歴テストタイミングデータを使用して、各シャードがほぼ同じ時間になるようにテストをシャード間で分散させます。ビルドフェーズは、CI システムが並列ランナーを生成するために使用する シャードマトリックス を出力します。
  2. テストフェーズ: 各 CI ランナーは シャードインデックス を受け取り、そのシャードに割り当てられたテストのみを実行します。最後のシャードは、他のシャードに割り当てられていないすべてのテストスイートも実行します。

ビルドフェーズ#

tuistPrepareTestShards タスクを使用してテストシャードを準備します:

bash
./gradlew tuistPrepareTestShards \
-PtuistShardMax=5

このタスクは次のことを行います:

  1. コンパイルせずに、シャーディング対象のテストタスクを持つプロジェクトを検出します
  2. 過去 30 日間にそれらのプロジェクト用に記録されたテストスイートとタイミングから、Tuist サーバー上でシャードプランを作成します
  3. CI システム用のシャードマトリックスを出力します

テストスイートがまだ記録されていない場合(例: 初回実行時)、プランにはすべてのテストを実行する単一のシャードが含まれます。

ビルドオプション#

Gradle プロジェクトプロパティを通じてシャーディングを設定します:

プロパティ説明
-PtuistShardMax=<N>最大シャード数(デフォルト: 2)
-PtuistShardMin=<N>最小シャード数
-PtuistShardMaxDuration=<MS>シャードあたりの目標最大期間(ミリ秒単位)
-PtuistShardTestTask=<NAME>テストをシャーディングするテストタスク(例: testDebugUnitTest)。デフォルトではビルド内のすべてのテストタスクが対象です

複数のビルドバリアントを持つ Android プロジェクトでは、バリアントごとに 1 つのユニットテストタスクがあります。シャードが実行するバリアントに合わせて -PtuistShardTestTask を設定し、そのバリアントを持つプロジェクトのみが計画されるようにします:

bash
./gradlew tuistPrepareTestShards \
-PtuistShardMax=5 \
-PtuistShardTestTask=testDebugUnitTest

シャード参照は CI 環境変数(GITHUB_RUN_ID、CI_PIPELINE_ID など)から自動的に導出されるか、TUIST_SHARD_REFERENCE 環境変数を通じて明示的に設定できます。

テストフェーズ#

各シャードランナーは、標準の test タスクを使用して割り当てられたテストを実行します。TUIST_SHARD_INDEX が設定されている場合、プラグインはサーバーからシャード割り当てを自動的に取得し、割り当てられたテストスイートのみを含むようにテスト実行をフィルタリングします。シャードに割り当てられたスイートがないプロジェクトのテストタスクはスキップされます。

最後のシャードはすべてのテストタスクを実行し、他のシャードに割り当てられたスイートを除外するため、新規追加されたものなど、まだ実行記録のないスイートも実行されます。

bash
TUIST_SHARD_INDEX=0 ./gradlew test

ビルドフェーズで -PtuistShardTestTask を設定した場合、各シャードで同じタスクを実行してください(例: ./gradlew testDebugUnitTest)。

継続的インテグレーション#

Tuist は以下の CI プロバイダーを自動的に検出します:

その他のプロバイダーの場合は、.tuist-shard-matrix.json ファイルを参照して並列ジョブを設定してください。

GitHub Actions#

マトリックス戦略を使用してシャードを並列に実行します:

yaml
name: Tests
on: [pull_request]
jobs:
build:
name: Build test shards
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.build.outputs.matrix }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- uses: gradle/actions/setup-gradle@v4
- run: tuist auth login
- id: build
run: ./gradlew tuistPrepareTestShards -PtuistShardMax=5
test:
name: "Shard #${{ matrix.shard }}"
needs: build
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard: ${{ fromJson(needs.build.outputs.matrix).shard }}
env:
TUIST_SHARD_INDEX: ${{ matrix.shard }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- uses: gradle/actions/setup-gradle@v4
- run: tuist auth login
- run: ./gradlew test

GitLab CI#

Tuist は .tuist-shard-child-pipeline.yml を生成し、これを 子パイプライン としてトリガーできます。生成されたシャードジョブが拡張する .tuist-shard テンプレートジョブを定義します:

yaml
# .gitlab-ci.yml
stages:
- build
- test
build-shards:
stage: build
script:
- tuist auth login
- ./gradlew tuistPrepareTestShards -PtuistShardMax=5
artifacts:
paths:
- .tuist-shard-child-pipeline.yml
test-shards:
stage: test
needs: [build-shards]
trigger:
include:
- artifact: .tuist-shard-child-pipeline.yml
job: build-shards
strategy: depend

子パイプラインには .tuist-shard ジョブテンプレートが必要です:

yaml
# .gitlab/shard-template.yml
.tuist-shard:
script:
- tuist auth login
- ./gradlew test

CircleCI#

Tuist は continuation orb 用のパラメータを含む .tuist-shard-continuation.json を生成します:

yaml
# .circleci/config.yml
version: 2.1
setup: true
orbs:
continuation: circleci/continuation@1
jobs:
build-shards:
docker:
- image: cimg/openjdk:17.0
steps:
- checkout
- run:
name: Build and plan shards
command: |
tuist auth login
./gradlew tuistPrepareTestShards -PtuistShardMax=5
- continuation/continue:
configuration_path: .circleci/continue-config.yml
parameters: .tuist-shard-continuation.json
workflows:
setup:
jobs:
- build-shards
yaml
# .circleci/continue-config.yml
version: 2.1
parameters:
shard-indices:
type: string
default: ""
shard-count:
type: integer
default: 0
jobs:
test-shard:
docker:
- image: cimg/openjdk:17.0
parameters:
shard-index:
type: integer
steps:
- checkout
- run:
name: Run shard
command: |
export TUIST_SHARD_INDEX=<< parameters.shard-index >>
tuist auth login
./gradlew test
workflows:
test:
jobs:
- test-shard:
matrix:
parameters:
shard-index: [<< pipeline.parameters.shard-indices >>]

Buildkite#

Tuist はシャードごとに 1 つのステップを含む .tuist-shard-pipeline.yml を生成します。buildkite-agent pipeline upload でアップロードしてください:

yaml
# pipeline.yml
steps:
- label: "Build test shards"
command: |
tuist auth login
./gradlew tuistPrepareTestShards -PtuistShardMax=5
buildkite-agent pipeline upload .tuist-shard-pipeline.yml

生成された各ステップには環境変数 TUIST_SHARD_INDEX が設定されています。共有スクリプトを使用して、各シャードステップにテストコマンドを追加します:

bash
# .buildkite/shard-step.sh
#!/bin/bash
tuist auth login
./gradlew test

Codemagic#

Codemagic は動的なマトリックスジョブをサポートしていないため、シャードごとに個別のワークフローを定義します。Tuist は各ワークフロー内で使用するために CM_ENV ファイルへ TUIST_SHARD_MATRIX と TUIST_SHARD_COUNT を書き込みます:

yaml
# codemagic.yaml
workflows:
build-shards:
name: Build test shards
instance_type: linux_x2
environment:
java: 17
scripts:
- name: Build and plan shards
script: |
tuist auth login
./gradlew tuistPrepareTestShards -PtuistShardMax=5
test-shard-0: &shard-workflow
name: "Shard #0"
instance_type: linux_x2
environment:
java: 17
vars:
TUIST_SHARD_INDEX: 0
scripts:
- name: Run shard
script: |
tuist auth login
./gradlew test
test-shard-1:
<<: *shard-workflow
name: "Shard #1"
environment:
java: 17
vars:
TUIST_SHARD_INDEX: 1
test-shard-2:
<<: *shard-workflow
name: "Shard #2"
environment:
java: 17
vars:
TUIST_SHARD_INDEX: 2
test-shard-3:
<<: *shard-workflow
name: "Shard #3"
environment:
java: 17
vars:
TUIST_SHARD_INDEX: 3
test-shard-4:
<<: *shard-workflow
name: "Shard #4"
environment:
java: 17
vars:
TUIST_SHARD_INDEX: 4

Bitrise#

Bitrise では、Tuist は .tuist-shard-matrix.json を BITRISE_DEPLOY_DIR に書き込み、下流のパイプラインステージでビルド成果物として利用できるようにします。事前に定義された並列ワークフローを持つ Bitrise Pipelines を使用します:

yaml
# bitrise.yml
pipelines:
test-pipeline:
stages:
- build-stage: {}
- test-stage: {}
stages:
build-stage:
workflows:
- build-shards: {}
test-stage:
workflows:
- test-shard-0: {}
- test-shard-1: {}
- test-shard-2: {}
- test-shard-3: {}
- test-shard-4: {}
workflows:
build-shards:
steps:
- script:
title: Build and plan shards
inputs:
- content: |
tuist auth login
./gradlew tuistPrepareTestShards -PtuistShardMax=5
test-shard-0: &shard-workflow
envs:
- TUIST_SHARD_INDEX: 0
steps:
- script:
title: Run shard
inputs:
- content: |
tuist auth login
./gradlew test
test-shard-1:
<<: *shard-workflow
envs:
- TUIST_SHARD_INDEX: 1
test-shard-2:
<<: *shard-workflow
envs:
- TUIST_SHARD_INDEX: 2
test-shard-3:
<<: *shard-workflow
envs:
- TUIST_SHARD_INDEX: 3
test-shard-4:
<<: *shard-workflow
envs:
- TUIST_SHARD_INDEX: 4
Tip

Bitrise は実行時に動的な並列ジョブ作成をサポートしていません。パイプラインステージで固定数のシャードワークフローを定義してください。ステージ内のワークフローは自動的に並列で実行されます。