Um Guia para JUnit 5

Um Guia para JUnit 5

*1. Visão geral *

JUnit é uma das estruturas de teste de unidade mais populares no ecossistema Java. A versão JUnit 5 contém uma série de inovações interessantes, com* o objetivo de suportar novos recursos no Java 8 e acima *, além de possibilitar muitos estilos diferentes de teste.

*2. Dependências do Maven *

A configuração de JUnit 5.x.0 é bastante simples, precisamos adicionar a seguinte dependência ao nosso pom.xml:

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.1.0</version>
    <scope>test</scope>
</dependency>

É importante observar que esta versão* requer que o Java 8 funcione *.

Além disso, agora há suporte direto para executar testes de unidade na plataforma JUnit no Eclipse e no IntelliJ. Obviamente, você também pode executar testes usando o objetivo do Teste Maven.

Por outro lado, o IntelliJ suporta o JUnit 5 por padrão. Portanto, executar o JUnit 5 no IntelliJ é bastante simples, basta clicar com o botão direito do mouse → Executar ou Ctrl-Shift-F10.

*3. Arquitetura *

O JUnit 5 é composto por vários módulos diferentes de três subprojetos diferentes:

====* 3.1 Plataforma JUnit *

A plataforma é responsável pelo lançamento de estruturas de teste na JVM. Ele define uma interface estável e poderosa entre a JUnit e seu cliente, como ferramentas de construção.

O objetivo final é como seus clientes se integram facilmente ao JUnit na descoberta e execução dos testes.

Ele também define a API TestEngine para desenvolver uma estrutura de teste executada na plataforma JUnit. Com isso, você pode conectar bibliotecas de teste de terceiros, diretamente no JUnit, implementando o TestEngine personalizado.

====* 3.2 JUnit Jupiter *

Este módulo inclui novos modelos de programação e extensão para gravação de testes no JUnit 5. Novas anotações em comparação com a JUnit 4 são:

  • _ @ TestFactory_ - indica um método que é uma fábrica de testes para testes dinâmicos

  • _ @ DisplayName_ - define o nome de exibição personalizado para uma classe de teste ou método de teste

  • _ @ Nested_ - indica que a classe anotada é uma classe de teste aninhada e não estática

  • _ @ Tag_ - declara tags para testes de filtragem

  • _ @ ExtendWith_ - é usado para registrar extensões personalizadas

  • _ @ BeforeEach –_ indica que o método anotado será executado antes de cada método de teste (anteriormente _ @ Before_)

  • _ @ AfterEach_ - indica que o método anotado será executado após cada método de teste (anteriormente _ @ After_)

  • _ @ BeforeAll_ - indica que o método anotado será executado antes de todos os métodos de teste na classe atual (anteriormente _ @ BeforeClass_)

  • _ @ AfterAll_ - indica que o método anotado será executado após todos os métodos de teste na classe atual (anteriormente _ @ AfterClass_) *_ @ Disable_ - é usado para desativar uma classe ou método de teste (anteriormente _ @ Ignore_)

====* 3.3 JUnit Vintage *

Suporta a execução de testes baseados no JUnit 3 e JUnit 4 na plataforma JUnit 5.

===* 4. Anotações básicas *

Para discutir novas anotações, dividimos a seção nos seguintes grupos, responsáveis ​​pela execução: antes dos testes, durante os testes (opcional) e após os testes:

====* 4.1 _ @ BeforeAll_ e _ @ BeforeEach_ *

Abaixo está um exemplo do código simples a ser executado antes dos principais casos de teste:

@BeforeAll
static void setup() {
    log.info("@BeforeAll - executes once before all test methods in this class");
}

@BeforeEach
void init() {
    log.info("@BeforeEach - executes before each test method in this class");
}

Importante notar é que o método com a anotação _ @ BeforeAll_ precisa ser estático; caso contrário, o código não será compilado.

====* 4.2 _ @ DisplayName_ e _ @ Disabled_ *

Vamos para novos métodos opcionais de teste:

@DisplayName("Single test successful")
@Test
void testSingleSuccessTest() {
    log.info("Success");
}

@Test
@Disabled("Not implemented yet")
void testShowSomething() {
}

Como podemos ver, podemos alterar o nome de exibição ou desativar o método com um comentário, usando novas anotações.

====* 4.3 _ @ AfterEach_ e _ @ AfterAll_ *

Por fim, vamos discutir métodos conectados às operações após a execução dos testes:

@AfterEach
void tearDown() {
    log.info("@AfterEach - executed after each test method.");
}

@AfterAll
static void done() {
    log.info("@AfterAll - executed after all test methods.");
}

Observe que o método com _ @ AfterAll_ também precisa ser um método estático.

===* 5. Asserções e premissas *

O JUnit 5 tenta tirar o máximo proveito dos novos recursos do Java 8, especialmente expressões lambda.

====* 5.1. Asserções *

As asserções foram movidas para org.junit.jupiter.api.Assertions e foram aprimoradas significativamente. Como mencionado anteriormente, agora você pode usar lambdas em asserções:

@Test
void lambdaExpressions() {
    assertTrue(Stream.of(1, 2, 3)
      .stream()
      .mapToInt(i -> i)
      .sum() > 5, () -> "Sum should be greater than 5");
}

Embora o exemplo acima seja trivial, uma vantagem de usar a expressão lambda para a mensagem de asserção é que ela é avaliada com preguiça, o que pode economizar tempo e recursos se a construção da mensagem for cara.

Agora também é possível agrupar asserções com assertAll () _, que relatará quaisquer asserções com falha dentro do grupo com um _MultipleFailuresError:

 @Test
 void groupAssertions() {
     int[] numbers = {0, 1, 2, 3, 4};
     assertAll("numbers",
         () -> assertEquals(numbers[0], 1),
         () -> assertEquals(numbers[3], 3),
         () -> assertEquals(numbers[4], 1)
     );
 }

Isso significa que agora é mais seguro fazer afirmações mais complexas, pois você poderá identificar a localização exata de qualquer falha.

====* 5.2 Premissas *

As premissas são usadas para executar testes somente se determinadas condições forem atendidas. Isso geralmente é usado para condições externas necessárias para que o teste seja executado corretamente, mas que não estão diretamente relacionadas ao que está sendo testado.

Você pode declarar uma suposição com assumeTrue () _, _assumeFalse () _ e _assumingThat () .

@Test
void trueAssumption() {
    assumeTrue(5 > 1);
    assertEquals(5 + 2, 7);
}

@Test
void falseAssumption() {
    assumeFalse(5 < 1);
    assertEquals(5 + 2, 7);
}

@Test
void assumptionThat() {
    String someString = "Just a string";
    assumingThat(
        someString.equals("Just a string"),
        () -> assertEquals(2 + 2, 4)
    );
}

Se uma suposição falhar, uma TestAbortedException é lançada e o teste é simplesmente ignorado.

Suposições também entendem expressões lambda.

===* 6. Teste de exceção *

Existem duas maneiras de testar exceções no JUnit 5. Ambos podem ser implementados usando o método _assertThrows () _:

@Test
void shouldThrowException() {
    Throwable exception = assertThrows(UnsupportedOperationException.class, () -> {
      throw new UnsupportedOperationException("Not supported");
    });
    assertEquals(exception.getMessage(), "Not supported");
}

@Test
void assertThrowsException() {
    String str = null;
    assertThrows(IllegalArgumentException.class, () -> {
      Integer.valueOf(str);
    });
}

O primeiro exemplo é usado para verificar mais detalhes da exceção lançada e o segundo apenas valida o tipo de exceção.

===* 7. Suítes de teste *

Para continuar os novos recursos do JUnit 5, tentaremos conhecer o conceito de agregação de várias classes de teste em um conjunto de testes para que possamos executá-las juntas. O JUnit 5 fornece duas anotações: _ @ SelectPackages_ e _ @ SelectClasses_ para criar suítes de teste.

Lembre-se de que, neste estágio inicial, a maioria dos IDEs não suporta esses recursos.

Vamos dar uma olhada no primeiro:

@RunWith(JUnitPlatform.class)
@SelectPackages("com.")
public class AllUnitTest {}

_ @ SelectPackage_ é usado para especificar os nomes dos pacotes a serem selecionados ao executar um conjunto de testes. No nosso exemplo, ele executará todos os testes. A segunda anotação, _ @ SelectClasses_, é usada para especificar as classes a serem selecionadas ao executar um conjunto de testes:

@RunWith(JUnitPlatform.class)
@SelectClasses({AssertionTest.class, AssumptionTest.class, ExceptionTest.class})
public class AllUnitTest {}

Por exemplo, a classe acima criará um conjunto que contém três classes de teste. Observe que as classes não precisam estar em um único pacote.

===* 8. Testes dinâmicos *

O último tópico que queremos introduzir é o recurso Testes dinâmicos do JUnit 5, que permite declarar e executar casos de teste gerados em tempo de execução. Ao contrário dos testes estáticos, que definem um número fixo de casos de teste no tempo de compilação, os testes dinâmicos nos permitem definir o caso de testes dinamicamente no tempo de execução.

Testes dinâmicos podem ser gerados por um método de fábrica anotado com @TestFactory. Vamos dar uma olhada no exemplo de código:

@TestFactory
public Stream<DynamicTest> translateDynamicTestsFromStream() {
    return in.stream()
      .map(word ->
          DynamicTest.dynamicTest("Test translate " + word, () -> {
            int id = in.indexOf(word);
            assertEquals(out.get(id), translate(word));
          })
    );
}

Este exemplo é muito direto e fácil de entender. Queremos traduzir palavras usando dois ArrayList, denominados in e out, respectivamente. O método de fábrica deve retornar um Stream, Collection, Iterable ou Iterator. No nosso caso, escolhemos o Java 8 Stream.

Observe que os métodos _ @ TestFactory_ não devem ser privados ou estáticos. O número de testes é dinâmico e depende do tamanho ArrayList.

===* 9. Conclusão*

A redação foi uma rápida visão geral das alterações que estão chegando com o JUnit 5.

Podemos ver que o JUnit 5 tem uma grande mudança em sua arquitetura relacionada ao iniciador de plataforma, integração com ferramenta de construção, IDE, outras estruturas de teste de unidade, etc. Além disso, o JUnit 5 é mais integrado ao Java 8, especialmente aos conceitos Lambdas e Stream.

Os exemplos usados ​​neste artigo podem ser encontrados no GitHub project.