Executando Testes Serenity JUnit 5 em Paralelo
Executar testes JUnit 5 em paralelo pode reduzir significativamente o tempo de execução dos testes. Este guia explica como configurar e otimizar a execução paralela para testes Serenity BDD usando JUnit 5.
Visão Geral
JUnit 5 (Jupiter) tem suporte nativo para execução paralela de testes. Quando combinado com Serenity BDD, você pode executar métodos de teste e classes de teste simultaneamente, aproveitando processadores multi-core.
Em uma máquina com 8 núcleos, a execução paralela pode reduzir o tempo de teste de 30 minutos para 5-8 minutos ou menos.
Pré-requisitos
- JUnit 5 (Jupiter)
- Serenity BDD 3.6.0 ou superior
- Código de teste thread-safe
- Recursos de sistema suficientes (CPU, RAM)
Configuração Básica
Passo 1: Habilitar Execução Paralela
Crie ou atualize junit-platform.properties em src/test/resources:
# Habilitar execução paralela
junit.jupiter.execution.parallel.enabled=true
Passo 2: Configurar Modo de Execução
# Executar classes de teste em paralelo
junit.jupiter.execution.parallel.mode.default=concurrent
# Executar classes de teste em paralelo
junit.jupiter.execution.parallel.mode.classes.default=concurrent
Passo 3: Configurar Estratégia de Execução
# Usar estratégia dinâmica (recomendado)
junit.jupiter.execution.parallel.config.strategy=dynamic
junit.jupiter.execution.parallel.config.dynamic.factor=1.0
Modos de Execução
Modo Concurrent
Testes executam em paralelo:
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.mode.classes.default=concurrent
Modo Same Thread
Testes executam sequencialmente:
junit.jupiter.execution.parallel.mode.default=same_thread
junit.jupiter.execution.parallel.mode.classes.default=same_thread
Modo Misto
Execute classes em paralelo mas métodos dentro de uma classe sequencialmente:
# Classes em paralelo
junit.jupiter.execution.parallel.mode.classes.default=concurrent
# Métodos na mesma classe executam sequencialmente
junit.jupiter.execution.parallel.mode.default=same_thread
Estratégias de Execução
Estratégia Dinâmica (Recomendada)
Determina automaticamente a contagem de threads com base nos processadores disponíveis:
junit.jupiter.execution.parallel.config.strategy=dynamic
junit.jupiter.execution.parallel.config.dynamic.factor=1.0
Cálculo do fator: threads = processadores × fator
Exemplos (em uma máquina de 8 núcleos):
- Fator
1.0= 8 threads (recomendado) - Fator
0.5= 4 threads - Fator
2.0= 16 threads
Estratégia Fixa
Usa um número específico de threads:
junit.jupiter.execution.parallel.config.strategy=fixed
junit.jupiter.execution.parallel.config.fixed.parallelism=4
Use quando:
- Você precisa de contagens de threads consistentes
- Executando em infraestrutura de CI/CD compartilhada
- Depurando problemas de execução paralela
Estratégia Personalizada
Implemente sua própria estratégia:
junit.jupiter.execution.parallel.config.strategy=custom
junit.jupiter.execution.parallel.config.custom.class=com.example.MyParallelStrategy
Controlando Paralelismo em Nível de Classe/Método
Configuração Por Classe
Use a anotação @Execution para controlar o paralelismo:
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
@ExtendWith(SerenityJUnit5Extension.class)
@Execution(ExecutionMode.CONCURRENT) // Executa os métodos desta classe em paralelo
class FastTests {
@Test
void test1() { }
@Test
void test2() { }
@Test
void test3() { }
}
Forçar Execução Sequencial
@ExtendWith(SerenityJUnit5Extension.class)
@Execution(ExecutionMode.SAME_THREAD) // Executa sequencialmente
class DatabaseMigrationTests {
@Test
void migration1() { }
@Test
void migration2() { } // Deve executar após migration1
}
Controle em Nível de Método
@ExtendWith(SerenityJUnit5Extension.class)
class MixedTests {
@Test
@Execution(ExecutionMode.CONCURRENT)
void canRunInParallel() { }
@Test
@Execution(ExecutionMode.SAME_THREAD)
void mustRunSequentially() { }
}
Bloqueios de Recursos
Previna acesso concorrente a recursos compartilhados:
import org.junit.jupiter.api.parallel.ResourceLock;
@ExtendWith(SerenityJUnit5Extension.class)
class SharedResourceTests {
@Test
@ResourceLock("database")
void accessDatabase1() {
// Acesso exclusivo ao banco de dados
}
@Test
@ResourceLock("database")
void accessDatabase2() {
// Vai aguardar accessDatabase1 completar
}
@Test
@ResourceLock(value = "database", mode = READ)
void readFromDatabase() {
// Acesso somente leitura - pode executar simultaneamente com outras leituras
}
}
Bloqueios de Recursos Comuns
import static org.junit.jupiter.api.parallel.Resources.*;
@Test
@ResourceLock(SYSTEM_PROPERTIES)
void modifiesSystemProperties() { }
@Test
@ResourceLock(SYSTEM_OUT)
void writesToSystemOut() { }
@Test
@ResourceLock("my-custom-resource")
void accessesCustomResource() { }
Thread Safety com Serenity
Gerenciamento do WebDriver
O @Managed WebDriver do Serenity é automaticamente thread-safe:
@ExtendWith(SerenityJUnit5Extension.class)
@Execution(ExecutionMode.CONCURRENT)
class WebTests {
@Managed(driver = "chrome", options = "headless")
WebDriver driver; // Cada thread recebe sua própria instância
@Test
void test1() {
driver.get("https://example.com");
// Thread-safe
}
@Test
void test2() {
driver.get("https://example.com/other");
// Instância de WebDriver independente
}
}
Bibliotecas de Passos
As bibliotecas de passos são thread-safe quando usadas corretamente:
@ExtendWith(SerenityJUnit5Extension.class)
@Execution(ExecutionMode.CONCURRENT)
class CheckoutTests {
@Steps
CartSteps cart; // Thread-safe - instância por thread
@Test
void addToCart() {
cart.addProduct("Product1");
}
@Test
void removeFromCart() {
cart.addProduct("Product2");
cart.removeProduct("Product2");
}
}
Evite Estado Mutável Compartilhado
// ❌ RUIM - Não é thread-safe
@ExtendWith(SerenityJUnit5Extension.class)
@Execution(ExecutionMode.CONCURRENT)
class BadTests {
private String sharedData; // Estado mutável compartilhado!
@Test
void test1() {
sharedData = "test1"; // Condição de corrida!
}
@Test
void test2() {
sharedData = "test2"; // Condição de corrida!
}
}
// ✅ BOM - Thread-safe
@ExtendWith(SerenityJUnit5Extension.class)
@Execution(ExecutionMode.CONCURRENT)
class GoodTests {
@Test
void test1() {
String localData = "test1"; // Variável local - thread-safe
// Usa localData
}
@Test
void test2() {
String localData = "test2"; // Independente
// Usa localData
}
}
Exemplo de Configuração Completa
junit-platform.properties Abrangente
# ==========================================
# Execução Paralela
# ==========================================
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.mode.classes.default=concurrent
# ==========================================
# Estratégia de Execução
# ==========================================
junit.jupiter.execution.parallel.config.strategy=dynamic
junit.jupiter.execution.parallel.config.dynamic.factor=1.0
# Alternativa: Estratégia fixa
# junit.jupiter.execution.parallel.config.strategy=fixed
# junit.jupiter.execution.parallel.config.fixed.parallelism=4
# junit.jupiter.execution.parallel.config.fixed.max-pool-size=8
# ==========================================
# Ciclo de Vida da Instância de Teste
# ==========================================
junit.jupiter.testinstance.lifecycle.default=per_method
# ==========================================
# Descoberta de Testes
# ==========================================
junit.jupiter.testclass.order.default=org.junit.jupiter.api.ClassOrderer$Random
# ==========================================
# Nomes de Exibição
# ==========================================
junit.jupiter.displayname.generator.default=org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores
Configuração Maven
Plugin Failsafe
Configure o plugin Maven Failsafe para execução paralela:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<!-- Habilitar JUnit 5 -->
<includes>
<include>**/*Test.java</include>
<include>**/*Tests.java</include>
</includes>
<!-- Propriedades do sistema -->
<systemPropertyVariables>
<webdriver.driver>${webdriver.driver}</webdriver.driver>
<environment>${environment}</environment>
</systemPropertyVariables>
<!-- Aumentar memória para execução paralela -->
<argLine>-Xmx2g</argLine>
</configuration>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
Executando pela Linha de Comando
# Executar todos os testes em paralelo
mvn clean verify
# Sobrescrever paralelismo
mvn clean verify -Djunit.jupiter.execution.parallel.config.fixed.parallelism=8
# Desabilitar execução paralela
mvn clean verify -Djunit.jupiter.execution.parallel.enabled=false
Configuração Gradle
test {
useJUnitPlatform()
// Habilitar execução paralela
maxParallelForks = Runtime.runtime.availableProcessors()
// Aumentar memória
minHeapSize = "512m"
maxHeapSize = "2g"
// Propriedades do sistema
systemProperty 'junit.jupiter.execution.parallel.enabled', 'true'
systemProperty 'junit.jupiter.execution.parallel.config.strategy', 'dynamic'
systemProperty 'junit.jupiter.execution.parallel.config.dynamic.factor', '1.0'
}
Otimização de Performance
1. Escolha a Estratégia Certa
Suítes de teste pequenas (<30 testes):
junit.jupiter.execution.parallel.config.strategy=fixed
junit.jupiter.execution.parallel.config.fixed.parallelism=2
Suítes de teste médias (30-100 testes):
junit.jupiter.execution.parallel.config.strategy=dynamic
junit.jupiter.execution.parallel.config.dynamic.factor=1.0
Suítes de teste grandes (>100 testes):
junit.jupiter.execution.parallel.config.strategy=dynamic
junit.jupiter.execution.parallel.config.dynamic.factor=0.75
2. Monitore o Uso de Recursos
# Monitorar enquanto os testes executam
top
htop
jconsole
Observe:
- CPU: Deve estar próximo de 100% de utilização
- Memória: Cada instância de navegador usa ~200-500MB de RAM
- Threads: Deve corresponder à sua configuração de paralelismo
3. Otimize o Design dos Testes
// ✅ BOM - Testes rápidos e focados
@Test
void shouldCalculateTotal() {
// Sem dependências externas, executa rapidamente
assertThat(calculator.add(2, 3)).isEqualTo(5);
}
// ⚠️ LENTO - Considere otimização
@Test
void shouldProcessLargeDataset() {
// Processa 10000 registros - considere usar dataset menor
}
4. Equilibre a Contagem de Threads
Poucas threads:
# Subutilizando CPU
junit.jupiter.execution.parallel.config.dynamic.factor=0.25
Muitas threads:
# Pode causar esgotamento de recursos
junit.jupiter.execution.parallel.config.dynamic.factor=4.0
Ótimo:
# Corresponde aos núcleos da CPU
junit.jupiter.execution.parallel.config.dynamic.factor=1.0
Depurando Testes Paralelos
Desabilitar Paralelismo Temporariamente
junit.jupiter.execution.parallel.enabled=false
Ou via linha de comando:
mvn verify -Djunit.jupiter.execution.parallel.enabled=false
Executar Classe de Teste Única
mvn test -Dtest=MyTestClass
Habilitar Log Detalhado
Adicione ao junit-platform.properties:
# Mostrar qual thread executa qual teste
junit.platform.output.capture.stdout=true
junit.platform.output.capture.stderr=true
No seu teste:
@BeforeEach
void logThread() {
System.out.println("Executando em: " + Thread.currentThread().getName());
}
Problemas Comuns e Soluções
Problema 1: Testes Instáveis
Sintomas: Testes passam individualmente mas falham em paralelo
Soluções:
- Verifique estado mutável compartilhado
- Verifique isolamento de dados de teste
- Revise bloqueios de recursos
- Adicione sincronização adequada
// Problema: Estado compartilhado
private static int counter = 0; // ❌ Não é thread-safe
@Test
void incrementCounter() {
counter++; // Condição de corrida
}
// Solução: Use tipos atômicos ou variáveis locais
private static AtomicInteger counter = new AtomicInteger(0); // ✅ Thread-safe
@Test
void incrementCounter() {
counter.incrementAndGet();
}
Problema 2: Esgotamento de Recursos
Sintomas: Testes ficam lentos ou falham após algum tempo
Soluções:
- Reduza a contagem de threads
- Aumente o tamanho do heap da JVM
- Verifique vazamentos de recursos
- Monitore os recursos do sistema
<!-- Aumentar tamanho do heap -->
<argLine>-Xmx4g</argLine>
Problema 3: Problemas com WebDriver
Sintomas: Instâncias de navegador se multiplicam ou não fecham
Soluções:
- Use
@ManagedWebDriver (Serenity gerencia o ciclo de vida) - Garanta limpeza adequada em
@AfterEach - Limite instâncias de navegador concorrentes
// ✅ Serenity gerencia o ciclo de vida
@Managed
WebDriver driver;
// ❌ Não gerencie manualmente em testes paralelos
WebDriver driver = new ChromeDriver(); // Risco de vazamento de memória
Problema 4: Bloqueios de Banco de Dados
Sintomas: Testes ficam em timeout aguardando banco de dados
Soluções:
- Use bloqueios de recursos
- Isole dados de teste
- Use banco de dados em memória para testes
@Test
@ResourceLock("database")
void modifiesDatabase() {
// Acesso exclusivo ao banco de dados
}
Boas Práticas
1. Comece Pequeno
Comece com 2-4 threads e aumente gradualmente:
junit.jupiter.execution.parallel.config.strategy=fixed
junit.jupiter.execution.parallel.config.fixed.parallelism=2
2. Isole Dados de Teste
@ExtendWith(SerenityJUnit5Extension.class)
class UserTests {
@Test
void createUser() {
String uniqueId = UUID.randomUUID().toString();
User user = createTestUser("user_" + uniqueId);
// Teste com usuário único
}
}
3. Use Tags para Paralelismo Seletivo
@Test
@Tag("parallel")
void canRunInParallel() { }
@Test
@Tag("serial")
@Execution(ExecutionMode.SAME_THREAD)
void mustRunSequentially() { }
# Executar apenas testes paralelos
junit.jupiter.includeTags=parallel
4. Limpe Recursos
@AfterEach
void cleanup() {
// Limpar dados de teste
testDataService.deleteTestData(testId);
}
5. Documente Thread Safety
/**
* Testes nesta classe modificam estado global da aplicação
* e devem executar sequencialmente.
*/
@Execution(ExecutionMode.SAME_THREAD)
class GlobalStateTests {
// ...
}
Medindo Performance
Antes da Execução Paralela
Total de testes: 150
Tempo de execução: 25 minutos
Após Execução Paralela (8 threads)
Total de testes: 150
Tempo de execução: 4 minutos
Speedup: 6.25x
Eficiência: 78%
Calcule métricas:
Speedup = Tempo Sequencial / Tempo Paralelo
Eficiência = Speedup / Número de Threads × 100%
Próximos Passos
- Retorne ao guia principal JUnit 5
- Aprenda sobre Organização de Testes
- Revise Boas Práticas
- Explore Relatórios Serenity