Fala pessoal beleza!
O nosso objetivo nesse post é entender o que é um teste unitário? Os testes unitários são pequenos métodos independentes que implementamos para testar alguma parte do nosso código. Digamos que nós temos um método na nossa classe que precise ser testado para garantir que ele funcione conforme o esperado. Pra garantir isso precisaremos implementar um ou mais testes unitários. Cada teste unitário é um método muito pequeno que criamos. É um pequeno pedaço de código que implementamos para invocar o método que está sendo testado.
Considerando a imagem acima, estou testando um método chamado generateCoupon. Então, o método JUnit que criarei invocará o método generateCoupon e validará se produziu um resultado esperado. Para testar o código da aplicação, geralmente implementamos mais de um teste unitário. Um teste unitário testará o nosso método com parâmetros válidos, enquanto outros testes unitários passarão intencionalmente parâmetros inválidos ao nosso método. E em ambos os cenários, os testes unitários validarão se o método que estivermos testando produz o resultado esperado.
O código dos testes unitários é muito simples e geralmente é muito curto, e é por isso que no lado direito imagem, ilustrei cada teste unitário com um elemento muito menor que o elemento que representa o método que está sendo testado no lado esquerdo. O método do lado esquerdo pode conter uma lógica de negócios muito complexa. E é por isso que eu optei por representa-lo com um elemento muito grande.
Simplificando, um teste unitário também é um código que você escreve, mas é muito pequeno e é um código muito simples que não contém muita lógica de negócios. É um código muito simples porque tudo o que ele faz é chamar um método e testá-lo usando parâmetros específicos e validar o resultado retornado.
É muito importante ter em mente que os testes unitários devem testar apenas uma funcionalidade específica. Não devemos tentar criar um único teste unitário que teste vários métodos da nossa classe ao mesmo tempo. Para implementar nossos testes unitários, usaremos o JUnit 5. O JUnit nos fornece uma API para implementar pequenos trechos de código para testar nossos métodos.
Conforme a imagem acima a nossa aplicação pode conter muitas classes e cada classe pode conter vários testes unitários para garantir que uma boa cobertura de testes e por consequência garantir o comportamento esperado. O ideal é implementarmos mais de um teste unitário para a maioria dos métodos da nossa classe.
Independente da IDE, pelo menos nas que eu já trabalhei, quando a execução do teste unitário for bem-sucedida ele será marcado com a cor verde. Mas quando o resultado não corresponde ao esperado ocorre uma falha e ele será marcado com uma cor vermelha. É por isso que nessa imagem temos alguns testes unitários coloridos em verde e alguns testes unitários que falharam coloridos em vermelho.
Avaliando mais de perto um único teste unitário. Inicialmente vamos começar com um exemplo de código muito simples e à medida que nos sentirmos mais à vontade com os testes unitários, veremos exemplos mais complexos. Vamos supor que temos uma classe chamada SimpleMath, e ela possui um método muito simples chamado sum que recebe dois parâmetros do tipo Double. Ele possui regras de negócio muito simples e tudo o que ele faz é realizar a soma dos dois parâmetros e retornar o resultado também do tipo Double. Mas mesmo sendo um exemplo de código muito simples, esse método ainda deve ser testado. Por exemplo, e se um desenvolvedor cometeu um erro de digitação e, em vez da soma, realizou uma multiplicação ou subtração? Pra prevenir isso podemos e devemos implementar testes unitários para garantir que esse código funcione corretamente. Para testar esse método, podemos implementar um teste unitário que se pareça com o trecho de código da imagem abaixo.
Nos próximos posts falarei de forma mais aprofundada sobre a estrutura dos testes unitários e todos os seus detalhes. Então, não se preocupe se por enquanto as coisas se parecerem um pouco estranhas ou confusas, pouco a pouco tudo fará sentido. Por exemplo, você deve ter notado que o nome do método parece muito incomum. É muito longo e tem anotação @Test. Mas não se preocupe, falaremos sobre isso nos próximos posts.
Observe, que este método é dividido em três seções principais. A primeira delas é chamada de Given ou Arrange. É nesta seção, que criamos uma nova instância da classe SimpleMath. A segunda seção é chamada de when ou act e é nesta seção que invocamos o método a ser testado sum passando a ele dois parâmetros válidos. O método sum que como você deve imaginar soma os dois Doubles é o método que estou testando. E ele é chamado de method under Test ou em uma tradução direta método em teste.
Claro queremos ter certeza de que esse método funciona conforme o esperado e realiza a soma corretamente. Então eu passo dois parâmetros e armazeno o valor retornado em uma nova variável chamada actual. Por fim temos a terceira seção que é chamada de then ou assert, e esta é uma seção onde eu uso métodos do JUnit para verificar se o resultado está correto. 6.2 somado com 2 deve produzir 8.2 como resultado. Então eu uso um método especial do JUnit chamado assertEquals para verificar se o resultado armazenado na variável actual está correto. O primeiro parâmetro que o assertEquals aceita é o valor esperado. E nesse caso esperamos que o resultado seja igual 8.2. O segundo parâmetro é um valor real que foi retornado do método de soma e o terceiro parâmetro é uma mensagem de erro opcional que será impressa no console se esse teste unitário falhar. Então, se 6.2 somado com 2 não produzir 8.2, significa que esse teste unitário irá falhar e a mensagem “6.2 + 2 did not produce 8.2!” será impressa no console. Como eu já disse, não se preocupe se você estiver se sentindo um pouco confuso e perdido neste momento. Nos próximos posts, iremos implementar testes unitários na prática, executá-lo e vermos como tudo funciona.
Conforme imagem acima se executarmos os testes unitários no meu ambiente de desenvolvimento, como por exemplo, na Spring Tool Suite ou no Eclipse e este teste unitário passar teremos uma barra verde. Mas se meu método de soma não retornar o resultado esperado e meu teste unitário falhar, veremos uma barra vermelha e claro haverão mais informações sobre a falha impressas no console e também no painel de resultados do teste. Claro, diferentes ambientes de desenvolvimento terão diferentes interfaces de usuário para os relatórios de teste, mas em essência os testes unitários bem-sucedidos sempre serão verdes e os testes unitários com falha sempre serão vermelhos.
Resumindo testes automatizados são um processo em que os desenvolvedores implementam e executam testes muito simples que testam partes individuais da aplicação. Os testes unitários são muito pequenos e são executados muito rapidamente. Eles rodam muito rápido porque, ao testar um método, quaisquer dependências que esse método possa ter são substituídas por objetos mockados. Considerando a imagem acima, se por exemplo o método que estou testando faz uma requisição HTTP e depende do objeto HTTPClient, um objeto HTTPClient real precisará ser substituído por uma versão fake ou mockada dele. E pode haver diferentes tipos desses objetos fakes. Pode haver um objeto fake, pode haver um objeto mockado ou pode ser um objeto spy. E claro falaremos mais sobre esses diferentes tipos de objetos fakes em posts futuros.
Então, conforme no diagrama acima o objeto do HTTPClient real será substituído pela versão mockada dele e nenhuma requisição HTTP real será enviada e isso fará com que nosso teste unitário seja executado muito mais rápidamente. Nós fazemos isso porque precisamos que o teste unitário não esteja realmente testando como um HTTPClient funciona. O teste que precisamos é focado apenas em testar o código dentro do método que estamos testando. Se o método que estivermos testando depende de outro objeto, a dependência precisará ser substituída por um objeto mockado. Quando todas as dependências externas forem substituídas por objetos mockados, com comportamento predefinido ou com valores codificados, o método que estamos testando funcionará muito rápido e nossos testes unitários também funcionarão muito rápido. E é por isso que, se necessário, podemos e devemos implementar mais de um teste unitário para testar um único método. Por exemplo, um teste unitário irá testar o método passando parâmetros válidos. Outros testes unitários serão para testar o método com parâmetro inválidos, e outros testes unitários podem ser para testar o método com resposta inválida do HTTPClient. Enfim podemos implementar diferentes testes unitários para garantir que o método que estamos testando funcione corretamente e seja confiável em diferentes condições e sempre produza o resultado esperado.
Bom creio que com esse breve início já deu pra ter ideia do quão importante é testar nosso código. Por esse post é isso e no próximo eu vou abordar diversos outros motivos para implementarmos testes unitários. Então fiquem ligados pra não perderem nada!