Escrevendo Asserções Básicas com JUnit Jupiter
Ao concluirmos esta aula, nós:
- Seremos capazes de escrever asserções utilizando a API padrão do JUnit Jupiter (
org.junit.jupiter.api.Assertions). - Compreenderemos como a ordem dos parâmetros evoluiu do JUnit 4 (
org.junit.Assert) para o JUnit Jupiter 6 e como usar avaliação tardia (lazy) de mensagens em Kotlin. - Compreenderemos por que, apesar dessas melhorias, ainda preferimos bibliotecas fluentes (como AssertJ) no dia a dia.
Escrevendo Asserções com a API Padrão do JUnit Jupiter
Se quisermos escrever asserções utilizando a API padrão embutida no JUnit Jupiter 6, devemos utilizar a classe org.junit.jupiter.api.Assertions (que substituiu a antiga classe org.junit.Assert do JUnit 4). Ela fornece diversos métodos estáticos para verificarmos resultados em nossos testes.
Antes de examinarmos cada método em detalhes, precisamos conhecer três regras fundamentais:
- Ordem dos valores comparados: Quando comparamos dois valores (ou objetos), devemos passá-los ao método de asserção sempre nesta ordem: primeiro o valor esperado (
expected) e depois o valor real obtido (actual). - Posição da mensagem de erro customizada: No antigo JUnit 4 (
org.junit.Assert), a mensagem de erro customizada (String) era passada como o primeiro parâmetro do método. No JUnit Jupiter 6 (Assertions), isso foi corrigido e padronizado: a mensagem de erro opcional é sempre o último parâmetro do método! - Suporte a Lambdas (Avaliação Lazy): No JUnit Jupiter 6, o último parâmetro pode ser tanto uma
Stringquanto um lambda() -> String. Em Kotlin, passar um lambda{ "mensagem..." }garante que a string de erro só será construída na memória se a asserção realmente falhar!
Vamos descobrir que tipos de asserções podemos escrever com a classe Assertions.
Asserções para Valores Booleanos
Se quisermos verificar que um valor Boolean é true, devemos utilizar o método assertTrue() da classe Assertions. Em outras palavras, podemos usar qualquer uma destas variações:
|
|
Se quisermos verificar que um valor Boolean é false, devemos utilizar o método assertFalse() da classe Assertions:
|
|
Asserções para Verificar se um Objeto É null ou Não É null
Se quisermos verificar que um objeto é null, devemos utilizar o método assertNull() da classe Assertions:
|
|
Se quisermos verificar que um objeto não é null, devemos utilizar o método assertNotNull() da classe Assertions:
|
|
Asserções de Igualdade entre Dois Valores ou Objetos
Se quisermos verificar que o valor (ou objeto) esperado é igual ao valor (ou objeto) real obtido (via método equals()), devemos usar o método assertEquals() da classe Assertions. Por exemplo, para comparar dois números inteiros (Int), podemos usar uma destas asserções:
|
|
Se quisermos verificar que o valor (ou objeto) esperado não é igual ao valor (ou objeto) real obtido, devemos usar o método assertNotEquals() da classe Assertions:
|
|
Asserções de Referência de Objetos (Identidade em Memória)
Se quisermos garantir que duas variáveis apontam exatamente para a mesma instância de objeto na memória (=== em Kotlin), devemos utilizar o método assertSame() da classe Assertions:
|
|
Se quisermos garantir que duas variáveis não apontam para a mesma instância na memória (!== em Kotlin), devemos utilizar o método assertNotSame() da classe Assertions:
|
|
Asserções de Igualdade entre Dois Arrays
Se quisermos verificar que dois arrays possuem exatamente os mesmos elementos na mesma ordem, devemos usar o método assertArrayEquals() da classe Assertions. Por exemplo, para comparar dois arrays de inteiros (IntArray), usamos:
|
|
Agora já sabemos como escrever diferentes tipos de asserções utilizando a API padrão do JUnit. Vamos entender por que, mesmo com as melhorias do JUnit Jupiter 6, não costumamos utilizá-la como nossa biblioteca principal de asserções.
Por Que Não Devemos Usar as Asserções “Padrão” do JUnit?
As asserções padrão do JUnit possuem dois problemas ergonômicos importantes:
- Precisamos passar ambos os valores (
expectedeactual) como parâmetros dentro do mesmo método estático (assertEquals(expected, actual)), sem assistência de autocompletar contextual da IDE específica para o tipo do objeto. - Precisamos passar os parâmetros em uma ordem que contraria o fluxo natural de leitura (
expectedantes deactual).
Uma das características mais importantes de uma boa API é proteger seus usuários contra erros acidentais de programação. Infelizmente, a API clássica assertEquals(expected, actual) falha nesse quesito.
Deixe-me explicar o motivo.
Como o método recebe os dois valores do mesmo tipo como parâmetros (assertEquals(a, b)), é crucial que a ordem dos argumentos seja natural para quem escreve o código. O problema é que, quando pensamos no teste, nosso cérebro raciocina na ordem: primeiro o valor obtido (actual), depois o valor esperado (expected).
A melhor maneira de demonstrar a diferença é comparar estas duas frases:
- O valor Y é retornado quando invocamos o método X.
- Quando invocamos o método X, o valor Y é retornado.
A primeira frase descreve a ordem exigida pela API clássica do JUnit (assertEquals(expected, actual)), enquanto a segunda frase descreve a ordem fluente e natural que queremos usar (assertThat(actual).isEqualTo(expected)).
Se você concorda que a segunda frase é muito mais natural, provavelmente já percebeu que usar assertEquals(expected, actual) exige atenção constante para não inverter os dois parâmetros. Mais cedo ou mais tarde, todo desenvolvedor acaba invertendo expected e actual por engano — e quando o teste falha, a mensagem de erro no console diz exatamente o oposto do que aconteceu (Expected: <valor errado> but was: <valor esperado>), causando enorme confusão!
Assertions do JUnit Jupiter sem problemas. Contudo, como a leitura fluente da esquerda para a direita (actual -> expected) é muito mais legível e segura, as demais aulas deste curso utilizarão bibliotecas fluentes como Hamcrest, AssertJ e Kotest Matchers.
Vamos resumir o que aprendemos nesta aula.
Resumo
Esta aula nos ensinou três pontos principais:
- Se quisermos escrever asserções usando a API nativa do JUnit Jupiter 6, utilizamos os métodos da classe
org.junit.jupiter.api.Assertions(substituta da antigaorg.junit.Assertdo JUnit 4), onde a mensagem de erro customizada passa a ser sempre o último argumento (suportando lambdas em Kotlin). - Aprendemos a verificar booleanos (
assertTrue/assertFalse), nulidade (assertNull/assertNotNull), igualdade (assertEquals/assertNotEquals), referências (assertSame/assertNotSame) e arrays (assertArrayEquals). - Evitamos usar
assertEquals(expected, actual)em projetos reais porque passar o valor esperado antes do valor real obtido é propenso a inversões acidentais e menos expressivo que APIs fluentes.