Escrevendo Testes Hierárquicos com @Nested no JUnit Jupiter
Depois de concluirmos esta aula bônus, nós:
- Saberemos como escrever testes aninhados (nested tests) no JUnit Jupiter utilizando a anotação
@Nested. - Compreenderemos como os métodos
@BeforeEache@AfterEachfuncionam e em qual ordem são invocados ao longo da hierarquia de classes internas.
1. Introdução à Nossa Classe de Teste Base
Na aula anterior, vimos como configurar métodos de setup e teardown em uma classe de teste plana. Durante esta aula, partiremos daquela mesma classe de teste base (JUnit5NestedExampleTest) e adicionaremos classes internas aninhadas com seus próprios métodos de setup, teardown e teste para observar o comportamento da hierarquia.
O código inicial da nossa classe de teste é o seguinte:
|
|
Vamos descobrir agora como adicionar classes de teste aninhadas a essa estrutura!
2. Escrevendo Testes Aninhados com @Nested
Vamos adicionar duas classes internas em níveis diferentes da nossa classe JUnit5NestedExampleTest para demonstrar a ordem de invocação dos métodos de setup, teardown e teste:
Passo 1: Adicionar a Primeira Classe Interna (inner class A)
Primeiro, adicionamos uma classe interna não-estática (inner class em Kotlin) chamada A dentro da nossa classe de teste raiz e a anotamos com @Nested. Essa anotação garante que o JUnit Jupiter reconheça e execute os testes dentro da classe interna!
Depois de criarmos a classe A, adicionamos um método @BeforeEach, um método @AfterEach e um método @Test dentro dela:
|
|
Três Detalhes Importantes sobre
@Nested:
- Classes internas não-estáticas (
inner classno Kotlin): Em Kotlin, uma classe declarada dentro de outra éstaticpor padrão. Para que o JUnit Jupiter consiga acessar o estado e os métodos@BeforeEachda classe externa (e reconhecer o@Nested), você deve declarar a classe aninhada com a palavra-chaveinner class.- Profundidade ilimitada: Não existe nenhum limite para a profundidade da hierarquia de classes
@Nested.- Uso de
@BeforeAlle@AfterAllem classes@Nested: Por padrão (quando o ciclo de vida da instância éPER_METHOD), métodos@BeforeAlle@AfterAllnão são usados em classes internas porque a JVM não permite membros estáticos eminner classes tradicionais sem@TestInstance(Lifecycle.PER_CLASS).
Passo 2: Adicionar um Segundo Nível de Aninhamento (inner class WhenX)
Segundo, adicionamos uma nova classe interna chamada WhenX dentro da classe A, anotando-a também com @Nested e @DisplayName("When X is true"). Dentro de WhenX, adicionamos seu próprio método @BeforeEach, @AfterEach e @Test:
|
|
Como Estruturar a Hierarquia no Mundo Real
O segredo para escrever testes hierárquicos limpos, fáceis de ler e fáceis de manter é construir a hierarquia de classes de forma a eliminar código de configuração duplicado:
- Em Testes Unitários: Uma classe de teste raiz contém todos os casos de teste de uma classe de produção (ex:
TodoCrudServiceTest). As classes@Nesteddo primeiro nível representam cada método testado (ex:FindById). E as classes@Nesteddo segundo nível representam cada cenário/pré-condição daquele método (ex:WhenTodoItemIsFoundeWhenTodoItemIsNotFound). - Em Testes de Integração e End-to-End: Uma classe de teste raiz geralmente foca em uma única funcionalidade/endpoint. Isso significa que podemos colocar as classes
@Nestedque representam os diferentes cenários diretamente no primeiro nível dentro da classe raiz!
3. Executando Nossos Testes Aninhados
Quando executamos a classe JUnit5NestedExampleTest, vemos a seguinte saída no console:
|
|
Observe a simetria perfeita na execução do teste sampleTestForMethodAWhenX():
- Os métodos
@BeforeEachsão executados de fora para dentro (Raiz ->A->WhenX). - O método
@Testé executado. - Os métodos
@AfterEachsão executados de dentro para fora (WhenX->A-> Raiz).
Isso significa que podemos colocar a configuração geral do SUT no @BeforeEach da classe raiz, a configuração comum ao método no @BeforeEach de A e o stub específico do cenário no @BeforeEach de WhenX, eliminando 100% da duplicação de código!
Resumo
Esta aula bônus nos ensinou cinco pontos fundamentais:
- Todas as classes de teste aninhadas devem ser classes internas não-estáticas (
inner classem Kotlin). - Todas as classes de teste aninhadas devem ser anotadas com
@Nested. - A profundidade da hierarquia de classes
@Nestednão possui limite. - Podemos adicionar métodos
@BeforeEache@AfterEachem qualquer nível da hierarquia@Nested(executados de fora para dentro no setup, e de dentro para fora no teardown). - Por padrão (sem
@TestInstance(Lifecycle.PER_CLASS)), não adicionamos@BeforeAlle@AfterAllem classes@Nested.