Guia de Troubleshooting e Migração do JUnit 5 → JUnit 6

A migração do JUnit 5 para o JUnit 6 representa uma evolução significativa no ecossistema de testes Java. Embora o JUnit 5 tenha estabelecido uma base modular e extensível, o JUnit 6 foca em otimizações de desempenho, aprimoramento de APIs de extensão e integração aprimorada com recursos modernos do Java 17+.
Este guia técnico fornece:
| Área | Status | Ação Requerida |
|---|---|---|
| JUnit Jupiter API | Maioria compatível | Atualizar imports e dependências |
| JUnit Platform | Melhorado | Atualizar configurações do launcher |
| Vintage Engine | Unificado sob nova plataforma | Remover ou substituir runners legados |
| Execução Paralela | Mais dinâmica | Revisar configuração de concorrência |
| Modelo de Extensões | Aprimorado | Refatoração opcional recomendada |
| Java Mínimo | Java 17+ | Atualizar JDK e build tools |
✅ Descoberta de testes mais consistente
✅ Melhor desempenho em suítes grandes
✅ Gerenciamento de extensões mais fácil
✅ Compatibilidade futura com versões Java
✅ Parser CSV mais robusto (FastCSV)
✅ Engine de execução unificado
Configuração recomendada com BOM:
XML<properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><junit.version>6.0.0</junit.version></properties><dependencyManagement><dependencies><dependency><groupId>org.junit</groupId><artifactId>junit-bom</artifactId><version>${junit.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><scope>test</scope></dependency></dependencies><build><pluginManagement><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>3.2.2</version><configuration><useModulePath>false</useModulePath></configuration></plugin></plugins></pluginManagement></build>
⚠️ IMPORTANTE: Remova estas dependências antigas:
XML<!-- Remover --><dependency><groupId>org.junit.platform</groupId><artifactId>junit-platform-launcher</artifactId></dependency><dependency><groupId>org.junit.platform</groupId><artifactId>junit-platform-runner</artifactId></dependency><dependency><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></dependency>
Erro:
TEXTjava.lang.NoClassDefFoundError: org/junit/platform/launcher/Launcher
Causa:
Dependências antigas do JUnit 5 (Platform 1.x) misturadas com JUnit 6. O Launcher foi reorganizado e módulos como junit-platform-runner e jfr foram removidos.
Solução:
Erro:
TEXTUnsupported class file major version 61java.lang.UnsupportedClassVersionError: has been compiled by a more recent version
Causa:
JUnit 6 requer Java 17 ou superior. Seu ambiente está usando Java 11 ou inferior.
Solução:
Maven:
XML<properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><maven.compiler.release>17</maven.compiler.release></properties>
IDE (IntelliJ IDEA):
CI/CD (GitHub Actions):
YAML- uses: actions/setup-java@v4with:distribution: 'temurin'java-version: '17'
Erro:
MAKEFILEorg.junit.jupiter.params.converter.ArgumentConversionException:Failed to convert String "value" to parameter of type ...
Causa:
O JUnit 6 adotou o FastCSV como parser interno. O comportamento é mais estrito:
Solução:
1. Corrija os arquivos CSV:
MAKEFILE# Incorretonome,idade,cidade"João,25,São PauloMaria,30,"Rio de Janeiro# Corretonome,idade,cidade"João",25,"São Paulo""Maria",30,"Rio de Janeiro"
2. Remova lineSeparator e ajuste a anotação:
JAVA// JUnit 5@CsvFileSource(resources = "/data.csv", lineSeparator = "\n")void test(String name, int age) { }// JUnit 6@CsvFileSource(resources = "/data.csv")void test(String name, int age) { }
3. Valide número de colunas:
JAVA// Incorreto@CsvSource({"1,2,3","4,5" // Faltando coluna!})// Correto@CsvSource({"1,2,3","4,5,6"})
Sintoma:
Build reporta "No tests found" ou testes não aparecem no runner da IDE.
Causa:
Filtros de descoberta mudaram no JUnit 6. Classes de teste podem não estar sendo detectadas.
Solução:
1. Verifique o padrão de nomenclatura:
JAVA// Detectado automaticamenteclass UserServiceTest { }class UserServiceTests { }class UserServiceSpec { }// Pode não ser detectadoclass TestUserService { }class UserServiceTestCase { }
2. Maven Surefire - ajuste a configuração:
XML<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>3.2.2</version><configuration><includes><include>**/*Test.java</include><include>**/*Tests.java</include><include>**/*Spec.java</include></includes></configuration></plugin>
3. IDE - force rebuild:
Sintoma:
Testes em classes @Nested executam em ordem diferente ou herdam ordenação inesperadamente.
Causa:
JUnit 6 introduziu herança de ordenação. Classes @Nested herdam automaticamente o @TestMethodOrder da classe externa.
Solução:
Opção 1 - Prevenir herança:
JAVA@TestMethodOrder(MethodOrderer.OrderAnnotation.class)class OuterTest {@Nested@TestMethodOrder(MethodOrderer.Default.class) // ✅ Sobrescreveclass InnerTest {@Test void test1() { }@Test void test2() { }}}
Opção 2 - Usar ordenação consistente:
JAVA@TestMethodOrder(MethodOrderer.OrderAnnotation.class)class OuterTest {@Nested@TestMethodOrder(MethodOrderer.OrderAnnotation.class) // ✅ Mesmo comportamentoclass InnerTest {@Test @Order(1) void test1() { }@Test @Order(2) void test2() { }}}
Erro:
MAKEFILENoSuchMethodError: org.junit.jupiter.api.extension.ExtensionContext.getStore(...)
Causa:
Bibliotecas externas (Mockito, Spring Boot Test, Quarkus) ainda usam APIs antigas do JUnit 5.
Solução:
1. Atualize as bibliotecas:
| Biblioteca | Versão Mínima |
|---|---|
| Spring Boot | ≥ 3.4.0 |
| Mockito | ≥ 5.11.0 |
| Quarkus | ≥ 3.12.0 (futuras versões) |
| AssertJ | ≥ 3.25.0 |
| TestContainers | ≥ 1.19.0 |
Exemplo Spring Boot:
XML<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.4.0</version></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency></dependencies>
2. Se a biblioteca ainda não tem suporte:
Erros comuns:
TEXTcannot find symbol: class ClasspathScanningSupportcannot find symbol: method getDiscoverySelectors()cannot find symbol: class BlacklistedExceptions
Causa:
JUnit 6 removeu APIs marcadas como @Deprecated desde versões 5.9.x e 5.10.x.
Solução - Tabela de Substituições:
| API Removida (JUnit 5) | Substituição (JUnit 6) |
|---|---|
| ClasspathScanningSupport | DiscoverySelectors.selectClasspathRoots() |
| BlacklistedExceptions | ExceptionUtils.throwAsUncheckedException() |
| InvocationInterceptor.interceptDynamicTest | Use DynamicTestInvocationContext |
| MethodOrderer.Alphanumeric | MethodOrderer.MethodName |
| TestInstanceFactoryContext.getTestClass() | getTestClassContext() |
| TestTemplateInvocationContext.getDisplayName() | getDisplayName(int) com índice |
| LauncherDiscoveryRequestBuilder | DiscoveryRequest.builder() |
| DynamicTest.stream(Supplier) | DynamicTest.of() |
Exemplos práticos 1:
JAVA// JUnit 5LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request().selectors(selectClass(MyTest.class)).build();// JUnit 6DiscoveryRequest request = DiscoveryRequest.builder().selectClass(MyTest.class).includeTags("integration").build();
Exemplo práticos 2:
JAVA// JUnit 5@TestMethodOrder(MethodOrderer.Alphanumeric.class)class MyTest { }// JUnit 6@TestMethodOrder(MethodOrderer.MethodName.class)class MyTest { }
Exemplos práticos 3:
JAVA// JUnit 5@TestFactoryCollection<DynamicTest> dynamicTests() {return Arrays.asList(DynamicTest.stream(Stream.of("A", "B"), name -> name, value -> assertTrue(true)));}// JUnit 6@TestFactoryStream<DynamicTest> dynamicTests() {return Stream.of(DynamicTest.of("Test A", () -> assertEquals(2, 1 + 1)),DynamicTest.of("Test B", () -> assertTrue("JUnit".startsWith("J"))));}
Sintoma:
Configurações de paralelismo, display names ou fail-fast não funcionam.
Causa:
Algumas chaves foram renomeadas ou unificadas no JUnit 6.
Solução - Chaves Atualizadas:
Arquivo: src/test/resources/junit-platform.properties
MAKEFILE# Paralelismo (inalterado)junit.jupiter.execution.parallel.enabled=truejunit.jupiter.execution.parallel.mode.default=concurrentjunit.jupiter.execution.parallel.config.strategy=fixedjunit.jupiter.execution.parallel.config.fixed.parallelism=4# Fail-fast (nova chave)junit.platform.execution.fail-fast=true# Removido (auto-detecção)# junit.jupiter.extensions.autodetection.enabled=true# Display namesjunit.jupiter.displayname.generator.default=org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores# Lifecycle defaultjunit.jupiter.testinstance.lifecycle.default=per_class# Condições desabilitadasjunit.jupiter.conditions.deactivate=org.junit.*DisabledCondition
Erro:
MAKEFILEjava.lang.module.FindException: Module org.junit.jupiter not found
Causa:
Com Java 17+, o sistema de módulos JPMS é mais rigoroso. É necessário declarar dependências no module-info.java.
Solução:
Arquivo: src/test/java/module-info.java
JAVAopen module com.exemplo.projeto {// Módulos necessáriosrequires org.junit.jupiter.api;requires org.junit.jupiter.params;// Para usar AssertJrequires org.assertj.core;// Para usar Mockitorequires org.mockito;// Exportar pacotes para testesexports com.exemplo.projeto to org.junit.platform.commons;opens com.exemplo.projeto to org.junit.platform.commons;}
Maven Surefire/Failsafe (≥ 3.0.0):
XML<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>3.2.2</version><configuration><useModulePath>true</useModulePath></configuration></plugin>
Erro:
TEXTUnrecognized option: --fail-fastError: Could not create the Java Virtual Machine.
Causa:
Versão antiga do junit-platform-console-standalone (5.x).
Solução:
1. Baixe a versão atualizada:
BASHwget https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone/6.0.0/junit-platform-console-standalone-6.0.0.jar
2. Execute com as novas opções:
BASHjava -jar junit-platform-console-standalone-6.0.0.jar \--fail-fast \--scan-classpath \--include-tag integration \--reports-dir build/test-results
3. Opções úteis do JUnit 6:
BASH# Execução paralela--config junit.jupiter.execution.parallel.enabled=true# Modo single--config junit.jupiter.testinstance.lifecycle.default=per_class# Selecionar classes específicas--select-class com.exemplo.MyTest# Incluir/excluir pacotes--include-package com.exemplo.integration--exclude-package com.exemplo.slow
Sintoma:
Testes com @RunWith(SpringRunner.class) ou @Test(expected = Exception.class) não executam.
Causa:
O Vintage Engine está deprecated e será removido. JUnit 6 incentiva a migração completa para Jupiter.
Solução - Tabela de Migração:
| JUnit 4 | JUnit 6 (Jupiter) |
|---|---|
| @RunWith(SpringRunner.class) | @SpringBootTest |
| @RunWith(MockitoJUnitRunner.class) | @ExtendWith(MockitoExtension.class) |
| @Before | @BeforeEach |
| @After | @AfterEach |
| @BeforeClass | @BeforeAll |
| @AfterClass | @AfterAll |
| @Test(expected = Exception.class) | assertThrows(Exception.class, ...) |
| @Test(timeout = 1000) | @Timeout(1) ou assertTimeout(...) |
| @Ignore | @Disabled |
| @Category(Integration.class) | @Tag("integration") |
Exemplos de migração:
JAVA// JUnit 4@RunWith(SpringRunner.class)@ContextConfiguration(classes = AppConfig.class)public class UserServiceTest {@Beforepublic void setup() { }@Test(expected = IllegalArgumentException.class)public void shouldThrowException() {service.divide(1, 0);}@Test(timeout = 1000)public void shouldComplete() { }@Ignore("Not implemented")@Testpublic void futureTest() { }}// JUnit 6@SpringBootTestclass UserServiceTest {@BeforeEachvoid setup() { }@Testvoid shouldThrowException() {assertThrows(IllegalArgumentException.class,() -> service.divide(1, 0));}@Test@Timeout(1)void shouldComplete() { }@Disabled("Not implemented")@Testvoid futureTest() { }}
Causas possíveis:
Soluções:
1. Ativar paralelismo:
TEXT# junit-platform.propertiesjunit.jupiter.execution.parallel.enabled=truejunit.jupiter.execution.parallel.mode.default=concurrentjunit.jupiter.execution.parallel.mode.classes.default=concurrentjunit.jupiter.execution.parallel.config.strategy=dynamicjunit.jupiter.execution.parallel.config.dynamic.factor=2
2. Usar lifecycle compartilhado quando seguro:
JAVA@TestInstance(Lifecycle.PER_CLASS)class UserServiceTest {private UserService service;@BeforeAllvoid setupOnce() {// Inicialização pesada uma única vezservice = new UserService(new ExpensiveResource());}@Testvoid test1() { }@Testvoid test2() { }}
3. Ordenação aleatória para melhor distribuição:
JAVA@TestMethodOrder(MethodOrderer.Random.class)class MyTests { }
4. Benchmark antes/depois:
BASH# JUnit 5mvn clean test -Dtest=UserServiceTest# [INFO] Tests run: 50, Time elapsed: 12.345 s# JUnit 6 sem otimizaçãomvn clean test -Dtest=UserServiceTest# [INFO] Tests run: 50, Time elapsed: 11.987 s# JUnit 6 com paralelismomvn clean test -Dtest=UserServiceTest \-Djunit.jupiter.execution.parallel.enabled=true# [INFO] Tests run: 50, Time elapsed: 4.231 s
JUnit 5:
JUnit 6:
Impacto:
JAVA// JUnit 5LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request().selectors(selectPackage("com.exemplo")).filters(includeClassNamePatterns(".*Test")).build();Launcher launcher = LauncherFactory.create();launcher.discover(request);// JUnit 6DiscoveryRequest request = DiscoveryRequest.builder().selectPackage("com.exemplo").includeClassNamePatterns(".*Test").includeTags("integration").excludeTags("slow").build();// Launcher é criado automaticamente pelo PlatformTestPlan plan = LauncherFactory.create().discover(request);
JUnit 5:
JAVApublic class TimingExtension implements BeforeAllCallback, AfterAllCallback {@Overridepublic void beforeAll(ExtensionContext context) {context.getStore(Namespace.GLOBAL).put("start", System.currentTimeMillis());}@Overridepublic void afterAll(ExtensionContext context) {long start = context.getStore(Namespace.GLOBAL).get("start", Long.class);System.out.println("Duration: " + (System.currentTimeMillis() - start));}}
JUnit 6:
JAVApublic class TimingExtension implements BeforeAllCallback, AfterAllCallback, OrderedExtension {@Overridepublic void beforeAll(ExtensionContext context) {context.getStore(Namespace.GLOBAL).put("start", System.currentTimeMillis());}@Overridepublic void afterAll(ExtensionContext context) {long start = context.getStore(Namespace.GLOBAL).get("start", Long.class);System.out.println("Duration: " + (System.currentTimeMillis() - start));}@Overridepublic int getOrder() {return 1; // ✅ Controle explícito de ordem}}
Benefícios:
1.1 Análise de Dependências
BASH# Maven - listar todas as dependências JUnitmvn dependency:tree | grep -i junit# Gradle./gradlew dependencies --configuration testRuntimeClasspath | grep -i junit
1.2 Backup e Branch
BASHgit checkout -b feature/junit6-migrationgit add .git commit -m "Checkpoint before JUnit 6 migration"
1.3 Documentar Configuração Atual
BASH# Salvar configuração atualcp pom.xml pom.xml.junit5.bakcp build.gradle.kts build.gradle.kts.junit5.bakcp src/test/resources/junit-platform.properties junit-platform.properties.bak
2.1 Maven
XML<!-- Remover TODAS as dependências JUnit 5 --><!-- Adicionar apenas: --><dependencyManagement><dependencies><dependency><groupId>org.junit</groupId><artifactId>junit-bom</artifactId><version>6.0.0</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><scope>test</scope></dependency></dependencies>
2.2 Gradle
KOTLINdependencies {testImplementation("org.junit.jupiter:junit-jupiter:6.0.0")}
2.3 Limpar Cache
BASH# Mavenmvn dependency:purge-local-repository -DreResolve=falsemvn clean# Gradle./gradlew cleanBuildCache./gradlew clean
3.1 junit-platform.properties
MAKEFILE# Revisar e atualizar chavesjunit.jupiter.execution.parallel.enabled=truejunit.jupiter.execution.parallel.mode.default=concurrentjunit.platform.execution.fail-fast=false# Remover chaves obsoletas# junit.jupiter.extensions.autodetection.enabled=true
3.2 Maven Surefire
XML<plugin><artifactId>maven-surefire-plugin</artifactId><version>3.2.2</version></plugin>
3.3 Gradle
KOTLINtasks.test {useJUnitPlatform()maxParallelForks = Runtime.getRuntime().availableProcessors()}
4.1 Identificar extensões:
BASHgrep -r "implements.*Callback" src/testgrep -r "implements.*Extension" src/test
4.2 Atualizar extensões customizadas:
JAVA// JUnit 5public class DatabaseExtension implements BeforeEachCallback {@Overridepublic void beforeEach(ExtensionContext context) {// Setup database}}// JUnit 6 - adicionar suporte a ordenaçãopublic class DatabaseExtension implements BeforeEachCallback, OrderedExtension {@Overridepublic void beforeEach(ExtensionContext context) {// Setup database}@Overridepublic int getOrder() {return 10;}}
4.3 Verificar uso de APIs deprecated:
BASH# Buscar por métodos deprecatedgrep -r "getTestClass()" src/testgrep -r "LauncherDiscoveryRequestBuilder" src/testgrep -r "Alphanumeric" src/test
5.1 Executar por módulo/pacote:
BASH# Maven - testar um módulo por vezmvn test -pl :modulo-core# Gradle - testar pacotes específicos./gradlew test --tests "com.exemplo.core.*"
5.2 Ativar logs detalhados:
XML<!-- Maven Surefire --><plugin><artifactId>maven-surefire-plugin</artifactId><configuration><trimStackTrace>false</trimStackTrace><redirectTestOutputToFile>true</redirectTestOutputToFile></configuration></plugin>
5.3 Analisar falhas:
BASH# Maven - ver relatório detalhadocat target/surefire-reports/*.txt# Gradlecat build/test-results/test/*.xml
6.1 Revisar todos os testes @CsvSource:
BASHgrep -r "@CsvSource" src/testgrep -r "@CsvFileSource" src/test
6.2 Validar arquivos CSV:
BASH# Script para validar CSVsfind src/test/resources -name "*.csv" -exec sh -c 'echo "Validating: $1"# Verificar aspas balanceadasawk -F"\"" "NF % 2 == 0 { print \"Unbalanced quotes in line \" NR \":\" \$0 }" "$1"' sh {} \;
6.3 Atualizar anotações:
JAVA@CsvFileSource(resources = "/test-data.csv",lineSeparator = "\n",numLinesToSkip = 1)@CsvFileSource(resources = "/test-data.csv",numLinesToSkip = 1)
7.1 GitHub Actions:
YAMLname: JUnit 6 Testson: [push, pull_request]jobs:test:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v4- name: Set up JDK 17uses: actions/setup-java@v4with:distribution: 'temurin'java-version: '17'cache: 'maven'- name: Run testsrun: mvn clean verify- name: Publish Test Reportuses: mikepenz/action-junit-report@v4if: always()with:report_paths: '**/target/surefire-reports/TEST-*.xml'
7.2 GitLab CI:
YAMLtest:image: eclipse-temurin:17-jdkstage: testscript:- ./mvnw clean verifyartifacts:when: alwaysreports:junit:- target/surefire-reports/TEST-*.xmlpaths:- target/surefire-reports/
7.3 Jenkins:
GROOVYpipeline {agent {docker {image 'eclipse-temurin:17-jdk'}}stages {stage('Test') {steps {sh './mvnw clean verify'}}}post {always {junit '**/target/surefire-reports/*.xml'}}}
8.1 Ativar paralelismo adequado:
MAKEFILE# junit-platform.propertiesjunit.jupiter.execution.parallel.enabled=truejunit.jupiter.execution.parallel.mode.default=concurrentjunit.jupiter.execution.parallel.mode.classes.default=concurrent# Estratégia dinâmica baseada em CPUjunit.jupiter.execution.parallel.config.strategy=dynamicjunit.jupiter.execution.parallel.config.dynamic.factor=1.5
8.2 Identificar gargalos:
JAVA@ExtendWith(TimingExtension.class)class SlowTest {@Testvoid slowOperation() {// Este teste será medido}}class TimingExtension implements BeforeEachCallback, AfterEachCallback {private static final Logger log = LoggerFactory.getLogger(TimingExtension.class);@Overridepublic void beforeEach(ExtensionContext context) {context.getStore(Namespace.GLOBAL).put(context.getUniqueId(), System.currentTimeMillis());}@Overridepublic void afterEach(ExtensionContext context) {long start = context.getStore(Namespace.GLOBAL).remove(context.getUniqueId(), Long.class);long duration = System.currentTimeMillis() - start;if (duration > 1000) {log.warn("{} took {}ms", context.getDisplayName(), duration);}}}
8.3 Usar @TestInstance(PER_CLASS) estrategicamente:
JAVA// Para testes que compartilham setup pesado@TestInstance(Lifecycle.PER_CLASS)class DatabaseIntegrationTest {private Database db;@BeforeAllvoid initDatabase() {// Setup caro executado UMA vezdb = new Database();db.migrate();}@Testvoid test1() { /* usa db */ }@Testvoid test2() { /* usa db */ }@AfterAllvoid cleanup() {db.close();}}
9.1 Criar documento de migração:
MARKDOWN# Migração JUnit 5 → 6## Data2024-XX-XX## Alterações Principais- ✅ Java 17 como versão mínima- ✅ JUnit 6.0.0- ✅ Removido Vintage Engine- ✅ Atualizado Spring Boot para 3.4.0- ✅ FastCSV como parser padrão## Problemas Encontrados1. **CSVs mal formatados**: Corrigidos 15 arquivos em `/test/resources`2. **Extensões customizadas**: Adicionado `OrderedExtension` em 3 extensões3. **APIs deprecated**: Substituído `MethodOrderer.Alphanumeric` por `MethodName`## Melhorias de Performance- Testes unitários: 12.3s → 4.2s (paralelismo ativado)- Testes de integração: 45s → 38s (lifecycle PER_CLASS)## Ações Pendentes- [ ] Atualizar Quarkus quando versão compatível for lançada- [ ] Revisar extensões de terceiros (biblioteca XYZ)
9.2 Atualizar README:
MARKDOWN## Requisitos- **Java 17+** (LTS recomendado: 17 ou 21)- **Maven 3.9+** ou **Gradle 8.5+**- **JUnit 6.0.0**## Executar Testes```bash# Todos os testesmvn test# Com paralelismomvn test -Djunit.jupiter.execution.parallel.enabled=true# Apenas testes de integraçãomvn test -Dgroups=integration
Execução com mave:
MAKEFILE---### Passo 10 - Revisão Final e Merge**10.1 Checklist de validação:**```bash# ✅ 1. Todos os testes passammvn clean verify# ✅ 2. Nenhuma dependência JUnit 5mvn dependency:tree | grep -i junit | grep -v "junit:6"# ✅ 3. Cobertura de código mantidamvn jacoco:report# Comparar com baseline anterior# ✅ 4. Build em ambiente limpodocker run --rm -v $(pwd):/app -w /app maven:3.9-eclipse-temurin-17 mvn clean verify# ✅ 5. Testes em diferentes SOs (opcional)# - Linux ✅# - Windows ✅# - macOS ✅
10.2 Code review:
BASH# Criar PRgit push origin feature/junit6-migration# Itens para revisão:# - Todas as dependências atualizadas?# - Configurações de CI/CD funcionando?# - Documentação atualizada?# - Testes passando em todos os ambientes?# - Performance igual ou melhor?
10.3 Merge e tag:
BASH# Após aprovaçãogit checkout maingit merge feature/junit6-migrationgit tag -a v2.0.0-junit6 -m "Migrated to JUnit 6"git push origin main --tags
JAVA@DisplayName("UserService")@TestInstance(Lifecycle.PER_CLASS)class UserServiceTest {private UserService service;private UserRepository repository;@BeforeAllvoid setupOnce() {// Setup global (executado uma vez)repository = new InMemoryUserRepository();}@BeforeEachvoid setup() {// Setup por testeservice = new UserService(repository);repository.clear();}@Nested@DisplayName("Criação de usuários")class UserCreation {@Test@DisplayName("Deve criar usuário com dados válidos")void shouldCreateUser() {User user = service.create("João", "joao@example.com");assertAll(() -> assertNotNull(user.getId()),() -> assertEquals("João", user.getName()),() -> assertEquals("joao@example.com", user.getEmail()));}@ParameterizedTest(name = "Email inválido: {0}")@ValueSource(strings = {"", " ", "invalid", "@example.com"})void shouldRejectInvalidEmail(String email) {assertThrows(ValidationException.class,() -> service.create("João", email));}}@Nested@DisplayName("Busca de usuários")class UserSearch {@BeforeEachvoid prepareData() {service.create("João", "joao@example.com");service.create("Maria", "maria@example.com");}@Test@DisplayName("Deve encontrar usuário por email")void shouldFindByEmail() {Optional<User> user = service.findByEmail("joao@example.com");assertTrue(user.isPresent());assertEquals("João", user.get().getName());}}}
JAVA@Nested@DisplayName("Validação de CPF")class CpfValidation {@ParameterizedTest(name = "CPF válido: {0}")@CsvSource({"111.222.333-44, true","123.456.789-09, true","000.000.000-00, false","111.111.111-11, false","123.456.789-00, false"})void shouldValidateCpf(String cpf, boolean expected) {assertEquals(expected, CpfValidator.isValid(cpf));}@ParameterizedTest@CsvFileSource(resources = "/cpf-test-cases.csv", numLinesToSkip = 1)void shouldValidateCpfFromFile(String cpf, boolean expected, String description) {assertEquals(expected, CpfValidator.isValid(cpf), description);}@ParameterizedTest@MethodSource("cpfProvider")void shouldValidateCpfFromMethod(CpfTestCase testCase) {assertEquals(testCase.expected(),CpfValidator.isValid(testCase.cpf()),testCase.description());}static Stream<CpfTestCase> cpfProvider() {return Stream.of(new CpfTestCase("111.222.333-44", true, "CPF válido comum"),new CpfTestCase("000.000.000-00", false, "CPF com todos zeros"),new CpfTestCase("123.456.789-00", false, "CPF com dígito verificador inválido"));}record CpfTestCase(String cpf, boolean expected, String description) {}}
JAVA// Extensão para medir tempo de execução@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@ExtendWith(TimingExtension.class)public @interface Timed {long maxMillis() default Long.MAX_VALUE;}class TimingExtension implements BeforeEachCallback, AfterEachCallback, OrderedExtension {private static final Namespace NAMESPACE = Namespace.create(TimingExtension.class);@Overridepublic void beforeEach(ExtensionContext context) {context.getStore(NAMESPACE).put("start", System.currentTimeMillis());}@Overridepublic void afterEach(ExtensionContext context) {long start = context.getStore(NAMESPACE).remove("start", Long.class);long duration = System.currentTimeMillis() - start;context.findAnnotation(Timed.class).ifPresent(timed -> {if (duration > timed.maxMillis()) {fail(String.format("Test exceeded time limit: %dms > %dms",duration, timed.maxMillis()));}});System.out.printf("[%s] %dms%n", context.getDisplayName(), duration);}@Overridepublic int getOrder() {return 100; // Executar após outras extensões}}// Uso@Timed(maxMillis = 1000)@Testvoid shouldCompleteQuickly() {// teste}
JAVAclass ConditionalTests {@Test@EnabledOnOs(OS.LINUX)@EnabledIfEnvironmentVariable(named = "CI", matches = "true")void linuxCiTest() {// Executado apenas em Linux no CI}@Test@DisabledIfSystemProperty(named = "skip.slow", matches = "true")void slowTest() {// Pode ser desabilitado com -Dskip.slow=true}@Test@EnabledIf("customCondition")void conditionalTest() {// Executado apenas se customCondition() retornar true}boolean customCondition() {return System.getenv("ENVIRONMENT").equals("development");}}
Maven Surefire Report:
XML<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-report-plugin</artifactId><version>3.2.2</version><executions><execution><phase>test</phase><goals><goal>report</goal></goals></execution></executions></plugin>
BASH# Gerar relatório HTMLmvn surefire-report:report# Disponível em: target/site/surefire-report.html
JaCoCo com JUnit 6:
XML<plugin><groupId>org.jacoco</groupId><artifactId>jacoco-maven-plugin</artifactId><version>0.8.11</version><executions><execution><goals><goal>prepare-agent</goal></goals></execution><execution><id>report</id><phase>test</phase><goals><goal>report</goal></goals></execution><execution><id>check</id><goals><goal>check</goal></goals><configuration><rules><rule><element>PACKAGE</element><limits><limit><counter>LINE</counter><value>COVEREDRATIO</value><minimum>0.80</minimum></limit></limits></rule></rules></configuration></execution></executions></plugin>
BASH# Script para comparar execuções#!/bin/bashecho "=== Test Execution Comparison ==="echo "JUnit 5 baseline: $(cat junit5-metrics.txt)"echo "JUnit 6 current: $(mvn test | grep 'Tests run' | tail -1)"# Salvar métricas atuaismvn test 2>&1 | grep -E "(Tests run|Time elapsed)" > junit6-metrics.txt
JAVA@TestInstance(Lifecycle.PER_CLASS)@Execution(ExecutionMode.CONCURRENT)class IsolatedTests {@Test@ResourceLock(value = "database", mode = ResourceAccessMode.READ_WRITE)void testRequiringExclusiveDbAccess() {// Acesso exclusivo ao banco}@Test@ResourceLock(value = "cache", mode = ResourceAccessMode.READ)void testReadingCache() {// Leitura concorrente permitida}}
JAVAclass FlakySafeTests {@RepeatedTest(value = 10, name = "Tentativa {currentRepetition}/{totalRepetitions}")void shouldBeConsistent() {// Executado 10 vezes para detectar flakinessint result = randomOperation();assertTrue(result >= 0);}@Test@Timeout(value = 5, unit = TimeUnit.SECONDS)void shouldNotHang() {// Garante que o teste não travapossiblyHangingOperation();}}
JAVAclass ResourceManagementTest {private AutoCloseable resource;@BeforeEachvoid setup() throws Exception {resource = createExpensiveResource();}@AfterEachvoid cleanup() throws Exception {if (resource != null) {resource.close();}}@Testvoid useResource() {// Garantia de limpeza mesmo com exceçõesassertDoesNotThrow(() -> resource.toString());}}
A migração do JUnit 5 para o JUnit 6 é um investimento estratégico que traz benefícios imediatos e de longo prazo:
⚠️ Nota Importante: Este é um guia baseado em uma versão hipotética do JUnit 6. Na realidade, a versão mais recente é JUnit 5.x (Jupiter). Use este guia como referência conceitual adaptando para sua versão real do JUnit.
🎉 Migração concluída com sucesso!
O que achou deste artigo?
Gostou do conteúdo?
Se este conteúdo te ajudou de alguma forma, considere fazer uma doação. Seu apoio me ajuda a continuar criando conteúdo de qualidade!