Ir al contenido

Fragmentación de tests de Gradle

El plugin de Gradle de Tuist incluye soporte integrado para la fragmentación de tests. Planifica las suites de test que Tuist registró en ejecuciones anteriores de CI de las tareas de test de tus proyectos y utiliza el servidor de Tuist para crear planes de fragmentos equilibrados basados en datos históricos de tiempos.

REQUIREMENTS

Cómo funciona#

La fragmentación de tests sigue un flujo de trabajo en dos fases:

  1. Fase de compilación: Tuist crea un plan de fragmentos en el servidor a partir de las suites de test registradas para tus proyectos. El servidor utiliza datos históricos de tiempos de test de los últimos 30 días para distribuir los tests entre los fragmentos, de modo que cada uno tarde aproximadamente lo mismo. La fase de compilación genera una matriz de fragmentos que tu sistema de CI usa para lanzar runners en paralelo.
  2. Fase de test: Cada runner de CI recibe un índice de fragmento y ejecuta solo los tests asignados a ese fragmento. El último fragmento también ejecuta todas las suites de test que no estén asignadas a otro fragmento.

Fase de compilación#

Prepara los fragmentos de test usando la tarea tuistPrepareTestShards:

bash
./gradlew tuistPrepareTestShards \
-PtuistShardMax=5

Esta tarea:

  1. Encuentra los proyectos que tienen la tarea de test a fragmentar, sin compilarlos
  2. Crea un plan de fragmentos en el servidor de Tuist a partir de las suites de test y los tiempos registrados para esos proyectos en los últimos 30 días
  3. Genera una matriz de fragmentos para tu sistema de CI

Cuando aún no hay suites de test registradas, por ejemplo en la primera ejecución, el plan tiene un único fragmento que ejecuta todos los tests.

Opciones de compilación#

Configura la fragmentación mediante propiedades del proyecto de Gradle:

PropiedadDescripción
-PtuistShardMax=<N>Número máximo de fragmentos (predeterminado: 2)
-PtuistShardMin=<N>Número mínimo de fragmentos
-PtuistShardMaxDuration=<MS>Duración máxima objetivo por fragmento en milisegundos
-PtuistShardTestTask=<NAME>Tarea de test cuyos tests se fragmentan, por ejemplo testDebugUnitTest. Por defecto, todas las tareas de test de la compilación

Los proyectos de Android con múltiples variantes de compilación tienen una tarea de test unitario por variante. Establece -PtuistShardTestTask a la variante que ejecutan tus fragmentos para que solo se planifiquen los proyectos que la tengan:

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

La referencia del fragmento se deriva automáticamente de las variables de entorno de CI (GITHUB_RUN_ID, CI_PIPELINE_ID, etc.) o se puede establecer explícitamente mediante la variable de entorno TUIST_SHARD_REFERENCE.

Fase de test#

Cada runner de fragmento ejecuta sus tests asignados usando la tarea estándar test. Cuando TUIST_SHARD_INDEX está establecida, el plugin obtiene automáticamente la asignación de fragmentos del servidor y filtra la ejecución de tests para incluir solo las suites asignadas. Las tareas de test en proyectos sin suites asignadas al fragmento se omiten.

El último fragmento ejecuta todas las tareas de test y excluye las suites asignadas a los otros fragmentos, de modo que las suites que aún no tienen ejecuciones registradas, como las recién añadidas, siguen ejecutándose.

bash
TUIST_SHARD_INDEX=0 ./gradlew test

Si estableces -PtuistShardTestTask en la fase de compilación, ejecuta esa misma tarea en cada fragmento, por ejemplo ./gradlew testDebugUnitTest.

Integración continua#

Tuist detecta automáticamente los siguientes proveedores de CI:

Para otros proveedores, consulta el archivo .tuist-shard-matrix.json para configurar trabajos paralelos.

GitHub Actions#

Usa una estrategia de matriz para ejecutar fragmentos en paralelo:

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 genera un .tuist-shard-child-pipeline.yml que activas como una pipeline secundaria. Define un trabajo de plantilla .tuist-shard que extiendan los trabajos de fragmento generados:

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

La pipeline secundaria necesita una plantilla de trabajo .tuist-shard:

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

CircleCI#

Tuist genera un .tuist-shard-continuation.json con parámetros para el orb de continuación:

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 genera un .tuist-shard-pipeline.yml con un paso por fragmento. Súbelo con 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

Cada paso generado tiene TUIST_SHARD_INDEX establecida en su entorno. Añade el comando de test a cada paso de fragmento usando un script compartido:

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

Codemagic#

Codemagic no admite trabajos de matriz dinámicos, así que define un flujo de trabajo separado por fragmento. Tuist escribe TUIST_SHARD_MATRIX y TUIST_SHARD_COUNT en el archivo CM_ENV para usarlos dentro de cada flujo de trabajo:

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#

En Bitrise, Tuist escribe .tuist-shard-matrix.json en el BITRISE_DEPLOY_DIR, haciéndolo disponible como artefacto de compilación para las etapas posteriores de la pipeline. Usa Bitrise Pipelines con flujos de trabajo paralelos predefinidos:

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 no admite la creación dinámica de trabajos paralelos en tiempo de ejecución. Define un número fijo de flujos de trabajo de fragmento en tus etapas de pipeline; los flujos de trabajo dentro de una etapa se ejecutan en paralelo automáticamente.