"Era uma vez, um jovem Aleatório que achou que existia muitos conteúdos legais sobre testes, mas resolveu adicionar seus dois centavos. O resto da história, vocês verão a seguir..."
O post de hoje será um dos mais legais e mais densos que eu já tive o prazer de fazer. Falaremos sobre um dos assuntos que eu mais gosto que são a̶s̶ ̶p̶r̶i̶n̶c̶e̶s̶a̶s̶ ̶d̶a̶ ̶D̶i̶s̶n̶e̶y̶ os testes. Juro que vou tentar ser o mais sucinto possível, mas conteúdo é tão legal que é capaz que eu me empolgue. Para conseguir me ajudar nessa tarefa, eu trouxe a minha princesa da Disney favorita:
O que é um teste?
No contexto de desenvolvimento, um teste de software consiste em um conjunto de ações que garantem que o software fará o que está se propondo a fazer. As ações realizadas variam de acordo com o projeto, a importância, o tempo disponível, complexidade, etc...
Nesse ponto, trago um revelação: colocar o código em produção para ser utilizado pelos clientes já é um teste. Não é o melhor teste, tem vários riscos, mas acaba acontecendo ;/
Num mundo ideal, nosso sistema tem boa cobertura e conseguimos garantir que a nossa aplicação funcione e encontramos os casos de erro antes dos clientes.
O que testar?
Esse ponto é polêmico e existem pessoas que defendem as mais diferentes opiniões. Alguns vão dizer que é para testar todos os casos possíveis, outros dirão que só precisa pegar os casos representativos, outros dirão que é necessário testar as interfaces, outros dirão que é preciso testar as aplicações usando sistemas reais outros dirão que os sistemas não precisam ser reais e por aí vai...
Como tudo na vida do dev, a resposta é: depende. Depende do tempo, recursos, tamanho do time, conhecimento e importância do seu projeto. No meu caso, eu prefiro testar o fluxo básico (também chamado de caminho feliz) com testes automatizados e alguns fluxos que eu acho que podem ocorrer erros.
Conforme a aplicação é utilizada e os bugs vão sendo encontrados, então adiciono mais e mais testes automatizados que simulem esses erros e essa parte esteja coberta. Afinal, errar é umano, mas tem limites.
Sempre achei que essa abordagem é bom ponto de começo para os testes, trazendo o equilíbrio bom entre o que deve ser testado e os recursos disponíveis.
O que não testar
Existe uma linha muito tênue entre você testar a sua aplicação e testar os frameworks que você está utilizando. O ideal é testar apenas o nosso código. E c̶o̶n̶f̶i̶a̶r̶ assumir que os frameworks estão bem testados e cobertos.
Para exemplificar isso, vamos imaginar um método que recebe uma princesa, verifica se o nome está nulo e salva no banco.
public void salvarPrincesa(Princesa princesa) {
if(princesa.getNome() == null) {
throw new RuntimeException("Princesa com nome nulo");
}
princesaDao.salvar(princesa);
}
Olhando esse método, você diria que um teste deve verificar se a princesa foi salva no banco?
A resposta é NÃO. Esse método não tem nenhuma relação com banco de dados. Ele lida com uma abstração (o DAO). Logo, um teste deve verificar se o DAO foi chamado. Se o DAO vai realmente salvar no banco, vai colocar em memória ou vai dar uma maçã envenenada para que a princesa durma para todo o sempre, não é problema da nossa regra e negócio. Nossa regra testa apenas nome nulo e passa adiante.
Caso seja necessário fazer um teste que verifica se o método salvar realmente salva no banco ou não, então esse teste deve ser feito em cima da classe PrincesaDao.
Tipos de testes
Da mesma forma que existem vários tipos de princesas, existem vários tipos de testes. E podemos classificar eles de diferentes formas. A lista abaixo traz algumas classificações possíveis.
Automatizados
Nesses testes, todo o processo de análise, execução dos testes e geração de relatórios é feito por um ou mais scripts diferentes. Isso permite que o mesmo teste possa ser repetido várias e várias vezes.
Manuais
Por outro lado, os testes manuais são testes realizados com a interação humana. Nem tudo pode ser validado através de scripts ou o custo de automatizar pode não valer a pena.
Testes funcionais
Esses testes buscam garantir que o sistema faz o que ele se propõe a fazer. Existem várias formas de fazermos esses testes e por isso os testes funcionais são divididos de acordo com a granularidade. A divisão que eu apresento é um pouco diferente do que é visto na litaratura. Normalmente, os testes mockeados ficam em algum limbo entre os testes unitários e os de integração.
Teste unitário
Esses testes são muito legais de fazer porque eles são rápidos, leves e permitem que você consiga cobrir um monte de situação diferente.
Porém, eles duas pegadinhas. A) Muito código que você faz não pode ser testado de forma unitária. Pois ele acaba tendo dependências com objetos externos. B) Existem coisas que você não consegue testar com teste unitário.
Teste mockeado
Uma saída para lidar com os objetos externos é criar um mock. Um objeto de mentirinha que vai reagir da forma que nós queremos e dando os retornos que nós esperamos. As duas principais bibliotecas de mock que nós temos é a Mockito e a Powermock.
Teste de integração
Ao invés de usar um mock para simular os objetos e ambientes externos, por que não usar algo de verdade?
Seguindo a linha dos 12 fatores para desenvolvimento de software, os ambientes de devsenvolvimento e produção devem ser semelhantes. Logo, se a aplicação funcionará usando um banco de dados de verdade, vamos testar usando um banco de dados de verdade.
Teste ponta-à-ponta
Esse é o suprassumo dos testes. Muitas pessoas são contra fazê-los porque eles são caros ou difíceis de serem realizados. Mas esses testes são os mais próximos da realidade.
Nesse tipo de teste levantamentos todos os sistemas e dependências de verdade. Buscando chegar o mais próximo do que é feito no mundo real.
Teste caixa preta
Essa nomenclatura serve mais para dizer que o teste é criado/planejado/executado sem saber as estruturas internas da aplicação.
Teste caixa branca
Ao contrário dos testes caixa-preta, nos testes caixa-branca, os testes são criados já tendo em mente as estruturas internas da aplicação.
Teste de performance
Esses testes buscam descobrir como é o desempenho do sistema em diferentes cenários.
Teste de carga
Visa medir os tempos de resposta e vazões do sistema em diferentes cargas.
Teste de stress
Semelhante a teste anterior em termos de execução, mas totalmente em sua filosofia. Esse teste busca tanto descobrir quais são os limites da aplicação bem como o comportamento do sistema atuando no limite.
Teste de regressão
Consiste em executar todos testes funcionais criados (ou demais testes quando relevante) para garantir que novas mudanças e alterações não introduzam novos erros.
Teste de segurança
Também conhecido com teste de penetração. Geralmente é feito por alguma empresa externa e visa encontrar falhas de segurança tanto no código quanto nos processos executados pela aplicação/empresa.
Teste de usabilidade
Mais focado para aplicações que tem telas, esse tipo de teste consiste em navegar pelas telas feitas (ou por ideias de telas) para verificar se elas atenderão o usuário.
Teste de aceitação
O sonho de consumo de todo o software. Os testes de aceitação vão dizer se o software está pronto ou não para receber o aceite do cliente.
Por que testar?
Qualidade. Fazer testes é o que permite dizer que nossa aplicação funciona ou não. Como vocês viram no outro tópico, existem várias formas de fazer isso.
Dicas gerais de como testar
Caso você seja a Aurora ou Cinderella, você tem uma fada madrinha que te dará uma mão para resolver os problemas. Caso você não seja, você precisa se ajudar.
Separei algumas dicas para trabalhar tanto em ferramentas que podem te ajudar a fazerem bonito nesse baile dos testes.
Faça que os testes rodem de forma rápida
Desenvolver é algo estressante então as coisas precisam ser rápidas. Se cada vez que você precisar rodar os testes, você levar 10 minutos esperando, a tendência é que você pare de testar.
Parando de testar, as coisas começam a dar ruim. Dando ruim, os prazos vão se apertar. Apertando os prazos, você vai tirar o que for menos necessárias para o sistema ir pra produção. Tirando o menos necessário, você tirará os testes e as coisas vão ficando pior. Então é MUITO importante que você consiga fazer com que os testes rodem rápido.
Escreva um código que facilite os testes
Se o seu código não for pensado para testes, quando você for escrevê-los você provavelmente terá dificuldades. Tendo dificuldade para testar, você vai parar de testar. Parando de testar, as coisas começam a dar ruim...
Evite o uso de static
Métodos estáticos como LocalDate.now()
ou Objects.isNull()
não podem ser mockeados facilmente. Então, se você precisa testar com algum comportamento específico, fica complicado.
Sempre que aparecer um método estático, pense se isso pode afetar os testes. Caso afete, utilize um objeto para encapsular esse método.
Saiba o porque do teste estar ali
Muitas vezes nós fazemos as coisas sem saber bem o que estamos fazendo ou o porquê das coisas. Com isso, nós podemos testar umas 10 vezes a mesma lógica e deixar de testar algo importante. Então, antes de escrever qualquer coisa crie algum tipo de "plano" onde você define o que você quer testar e o motivo desse teste.
Utilize ferramentas para verificar a cobertura de testes
Se nós não conseguimos medir, nós nem podemos dizer se estamos fazendo um bom trabalho (provavelmente, nosso trabalho está ruim).
O JaCoCo é uma ferramenta ótima para dizer como está nossa cobertura. O que estamos esquecendo e o que precisamos melhorar.
Mas não se prenda muito a isso
É importante ter uma boa cobertura de teste. Particularmente, eu acho que 80% para um projeto maduro é um bom número. Existem coisas que simplesmente são caras demais para serem testadas, ou que só não tem motivo para testar.
Além disso, buscar os 100% pode dar a falsa sensação de que você tem todos os casos possíveis mapeados e que nunca vai acontecer um erro. Spoiler: a menos que o seu nome seja Mulan e você esteja lutando contra os Hunos, você vai errar.
Considerações
Esse post foi mais teórico. Nos próximos, vamos ver como fazer testes unitários, mockeados e de integração. Tudo isso trabalhando com Quarkus. Aguardem a continuação /o/
Top comments (1)
Excelente artigo, super didático!