Tudo sobre Testes - Testes Unitários vs. Testes de Integração vs. Testes E2E

Ricardo Pedroni
RPEDRONI
Published in
10 min readJun 18, 2021

Pelo jeito esse post é sobre testes.

Fala, galera! Aqui é o Professor Ricardo Pedroni e vamos juntos para mais uma aula fantástica 🎉
🎥 Não esqueça de ver essa aula e muitas outras no nosso Canal do YouTube!

Já que você está aqui, vamos ter uma conversa sincera?

Eu quero que você seja sincero comigo e diga a verdade. Não adianta mentir porque sendo professor, eu sempre sei a verdade. Sempre.

“Se aplicarmos essa equação podemos naturalmente concluir que o Carlos colou na prova.”

Responde aí: aposto que você sabe que fazer testes e testar bem seu projeto é importantíssimo.. mas você odeia fazer isso, né? Eu aposto que você odeia mesmo, seu monstro – mas agradeço sua sinceridade.

Sempre me questionei por que desenvolvedores não gostam de testes e a resposta é simples. O que nos motiva a virar engenheiros, programadores, arquitetos e desenvolvedores é nosso poder adquirido de criar e construir – a emoção de desenvolver está em ver coisas novas, concretizar aquilo que antes não existia.

Testes não fazem isso. Tipicamente testes são ensinados como “mera consequência”, algo que você faz porque você tem vergonha de não fazer, o setor de QA pediu ou.. você só não faz mesmo porque FODASSE TUDO #YOLO

vida loka rende foto boa pro insta

Seja lá o motivo, tendemos a não gostar de testes porque não aprendemos a fazê-los da forma correta – mas vamos corrigir isso hoje. Esse post servirá para dar um panorama geral de que tipos de testes existem, para que servem e como e onde devem ser usados ~ bora lá ✨

Tipos de Testes

Uma analogia que gosto bastante para os tipos de testes é uma que vi num post do Kent Dodds. Basicamente testes e os tipos de testes são comparados com tinta e os pinceis usados para pintar uma parede. Imagine que você quer pintar a parede do seu quarto na cor da estação (Pantone me falou que é o “amarelo vibrante”). Para pintar toda a parede você pode usar um rolo grande, um pincel pequeno ou usar um jato de tinta. Usar apenas um provavelmente vai gerar um resultado ruim, ineficiente ou ambos. Se usar só o rolo grande, vai ter dificuldade de pintar os cantos e onde precisa de mais detalhes enquanto só usar o pincel fino demora demais, enquanto usar o jato de tinta.. bem, nunca pintei usando jato de tinta, mas certeza que ia fazer merda. Enfim, para dar cobertura de tinta adequada a sua parede, é necessário que use as ferramentas adequadas.

No mundo dos testes, temos os pinceis para cada nível de cobertura que queremos dar. Começando do mais fino ao mais grosso 😏:

  • Testes Unitários: são testes que vão cobrir uma unidade de funcionamento da sua aplicação. O conceito de unidade é abstrato mas tipicamente vai ser um módulo com um propósito próprio, como um uma função ou um componente Button em React. Nos testes unitários, normalmente tudo que podemos remover de conexões com o “mundo externo”, vamos remover, e colocar valores de teste (mocks) no lugar;
  • Testes de Integração: quando for necessário testar múltiplas unidades juntas para ver se integradas umas às outras funcionam corretamente podemos escrever testes de integração. Aqui já tentamos usar menos mocks (i.e. deixamos as conexões mais “reais” entre si) para assimilar-se a um uso mais real. Um exemplo de teste de integração é ver se um formulário de login funciona (e.g. se o componente Button junto com Input e Form vão chamar sua callback se os valores de entrada são válidos);
  • Testes Funcionais ou End-to-End (E2E): os rolos grandões, conhecido como “End-to-End” porque testam de ponta a ponta (no pt-BR formal, “de cabo a rabo”). Esses testes vão testar se uma funcionalidade completa da sua aplicação está de acordo, idealmente sem nenhum mock utilizado. A ideia é replicar ao máximo a experiência do seu usuário, como se fosse um robô no lugar dele fazendo as mesmas tarefas e vendo se é possível ou não completar de forma correta. Um exemplo pode ser verificar se um usuário consegue entrar na página do perfil dele, detectar se aparece o formulário de login, entrar com os dados dele, fazer o login e ver se ele é roteado para a página privada /my-profile após isso tudo caso os dados estiverem corretos, ou se voltamos para /login caso contrário.

Repare que existe bastante mistura do que é um teste ou outro. É normal que se escreva um teste de integração que faz um pouco o papel de um teste unitário ou um teste E2E que fará uma pequena parte como um teste de integração, e não tem problema. Uma recomendação que daria é evitar que testes menores (i.e. unitário) façam o papel de testes maiores, justamente para manterem o escopo definido e para que mantenham seu custo baixo. Sim, é isso mesmo, cada teste tem custo.

Custo? CUSTO? Já odeio fazer e ainda preciso pagar pra ter?

Com esse dinheiro, dá para comprar exatamente 23 testes.

Calma calma, Pedrinho, antes de ir vender seus BTCs para poder testar sua aplicação, vamos discutir o que é o conceito de custo no mundo de testes.

O Custo de Testes

Cada teste possui um custo próprio. O custo de um teste não é monetário (essa afirmação é parcialmente verdade, mais sobre isso depois) mas sim no tempo necessário para elaborar, escrever e manter o teste e o custo do tempo de execução desse teste. O custo do tempo de criação e manutenção do teste é evidente — quanto mais testes forem criados, mais tempo irá investir neles. Já sobre o custo do tempo de execução de um teste, lanço para você uma pergunta:

Que tipo de teste tem maior custo: um teste unitário, um teste de integração ou teste E2E?

Se você respondeu teste unitário, parabéns! Você errou! ✨

Os testes mais custosos de tempo de execução são os testes E2E. A razão por trás disso é simples: como um teste E2E vai testar um pedaço grande da aplicação, é necessário que a aplicação esteja em execução, significando que uma instância dela deverá “estar rodando”, possivelmente um backend/API vai precisar ser iniciado também para ter as conexões funcionais, e se esse teste for ser executado numa pipeline de CI/CD, haverá a necessidade de instalar todas as dependências do seu projeto e provavelmente fazer um build da versão final para poder executá-lo naquele ambiente. Por isso que além do custo de tempo mencionei a possibilidade de custo monetário, uma vez que executar um teste em CI requer algum tipo de computação adicional, incluindo algum sistema de integração continua — e isso vai te custar (tempo e alguns centavos de dólar aqui e ali).

Cobertura de Testes

Ouve-se muito o termo cobertura quando estamos falando de testes. Mas o que exatamente queremos dizer com isso?

Sorvete com uma colher de sopa de testes unitários.

Cobertura é uma métrica usada para saber quantos porcento (%) do nosso código está sendo testado. Normalmente usamos os testes unitários como base para esse cálculos. De forma genérica, a cobertura é a porcentagem do número de linhas que são testadas dividido pelo número total de linhas que existem no seu projeto, que resulta em um número. Esse cálculo pode ser menos ou mais preciso se a ferramenta de cobertura analisar individualmente as funções e ramificações do seu código e pode variar um tanto dependendo do método que escolher.

Dito tudo isso e sendo cobertura um número que identifica quanto do seu código está testado, desafio você à próxima polêmica pergunta:

Numa escala de 0 a 100%, quantos porcento do seu código deve estar coberto por testes?

Pensa nessa um pouco antes de responder…

Talvez como as bem intencionadas almas que vieram antes de você, você respondeu 100%. Primeiramente, quero te parabenizar pela boa intenção — querer ter um código completamente testado é fantástico e mostra o seu compromisso com qualidade e profissionalismo.

“MAS QUE CARALHA, PROFESSOR. A IDEIA NÃO É TESTAR PRA GARANTIR COISA BOA?”

- Albert Einstein, 1927

Ótimo pergunta mas a resposta é mais complicada que isso. Veja bem, lembra que nós conversamos sobre o custo de testes agora pouco e concordamos que um teste tem um custo de criação, manutenção e execução. As leis da economia ditam que se algo possui um custo é natural que se tenha uma quantidade limitada desse item. Inclusive, para testes, existe um declínio do retorno de valor de adicionar testes após um dado ponto.

A cobertura de testes necessária e o ponto de declínio de retorno depende da especificação do projeto. Projetos críticos (hospitalares, aeroespaciais, etc.) normalmente terão coberturas maiores justamente para garantir que todos os “pedaços” estejam devidamente testados, inclusive com possíveis redundâncias e testes de casos extremamente improváveis mas que seriam fatais caso acontecessem.

No entanto, a maioria dos projetos não cai dentro dessas categorias e portanto o custo de testar excessivamente tem valor. Afinal, de quantas formas diferentes podemos testar um componente Button no seu projeto React para garantir que ele funciona corretamente? Precisamos fazer um teste para garantir que ele continua funcionando num browser IE6 rodando num Windows 98 sem javascript numa madrugada de maio?

<Button color=”red”>Launch Nuke</Button> com poucos testes

Sei lá, o projeto e o tempo são seus. Mas lembre que quanto mais teste seu projeto tiver, em geral mais qualidade terá, ao custo de maior tempo de criação e execução.

Mas, professor, eu quero o número mágico! Por isso que paguei por esse curso!

Ok, vou te falar, mas só porque você pagou por esse curso.

Ao longo dos anos, pela minha experiência e troca de experiência com outros profissionais, um número genérico que tento seguir quando não temos uma especificação melhor é 70%. O raciocínio por trás disso é que 70% cobre a maioria do projeto, especialmente as partes mais críticas e importantes, sem excessivamente demandar que todo e qualquer pedaço do projeto possua cobertura.

É importante destacar que isso não significa que tem várias partes do seu projeto sem nenhum teste — isso significa que pode ter casos de uso dele sem teste. Por exemplo, se uma dada função se comporta de algumas maneiras mas pode gerar um valor diferente em alguns casos bem específicos e esses casos não são muito importantes ou são difíceis de testar mas são meio evidentes que vão funcionar, talvez o investimento em testar essa parte não vale o tempo. Mas, novamente, tenha bastante zelo em analisar cada caso.

Outra recomendação genérica é limitar a quantidade de testes E2E que for fazer. Como comentei antes, é possível que um teste E2E seja praticamente um teste unitário, dada suas capacidades. Mas lembre também que testes E2E são os mais custosos para executar uma vez que o ambiente de execução inteiro estará de pé para isso. Consequentemente procure tentar cobrir as “rotas” ou casos de uso mais essenciais ou importantes da sua aplicação e vá adicionando esses testes gradativamente em outras partes, caso sentir que elas estejam pouco protegidas de erros ou mesmo para garantir que um bug que apareceu esteja consertado e não venha a ter regressão.

Fato curioso: num projeto recente meu, nossos testes E2E rodando no CI demoravam em média 45 minutos para executarem. Isso significa que para que seu código seja aceito para ser mergeado ao master, você precisaria esperar quase 1 hora e ainda, caso houvesse algum erro, você precisaria consertar esse erro e começar tudo de volta 🎉 Alegria pura — inclusive nesse projeto tomamos a (controversa) decisão de remover alguns testes E2E que cobriam áreas bastante não-importantes para diminuir um pouco esse tempo. Caiu para ~30min (velocidade da luz praticamente).

Distribuição de Testes

Já que estamos falando de cobertura e tipos de testes, uma pergunta que se pode fazer é em quais testes devemos dar mais importância.

Unitários porque validam as partes menores? Integração porque garantem bom comportamento entre as partes? Ou E2E que garantem que sua aplicação funcione da forma que você espera que seus usuários a usem?

Igual a quando falamos sobre cobertura, responder isso aqui de uma forma exata é difícil. Mas já que estamos apelando para genéricos, eu diria que uma distribuição saudável seria:

  • Testes Unitários: garanta que as unidades principais funcionem corretamente, teste todas as outras unidades com testes básicos, pode pular testes difíceis de realizar nas não-essenciais → ~30% dos esforços
  • Testes de Integração: → garanta que as combinações críticas estejam conversando adequadamente (i.e. sem elas, o usuário não conseguiria usar nada), evite mockear o que puder → ~50% dos esforços
  • Testes E2E: garanta que os casos de uso principais estejam cobertos, adicione testes de funcionamento necessário (“smoke tests”) → ~20% dos esforços

Novamente, analise isso para seu projeto específico.

Tenho até medo de colocar números assim porque existem 3 tipos de desenvolvedores:

  1. Os racionais: vão entender que isso tudo que foi falado são diretrizes e recomendações de boas práticas, e vão usar de forma racional nos seus projetos, mas sempre lembrando que cada caso é um caso;
  2. Os fanáticos: “TEM QUE SER 20% DE E2E PORQUE O CARA LÁ DO POST FALOU QUE TEM QUE SER 20%. COMO ASSIM TÁ ERRADO? O CARA É PROFESSOR PORRA! 20% OU VOU EMBORA”. Aí passa uma semana criando uma calculadora para garantir que passou 20% do tempo em testes E2E (e 80% criando a calculadora);
  3. Os chatopaporra: “Veja bem, eu trabalhei na Amagoogleappleflix e lá nós usamos 50% de testes unitários com 2 revisões manuais feito por um time de QA dedicado. 50% é a resposta correta”.

Eu já trabalhei em projetos que ~90% dos testes eram unitários e o resto E2E, sem nenhum tipo de teste de integração e tudo deu certo. Eu já trabalhei em projeto que não tinha NADA de testes e deu tudo certo… huehue mentira projeto sem teste sempre dá merda uma hora — adicionamos um monte de teste unitário depois e… ajudou (saudades da época de estagiário ❤).

Pare, pense e veja o que é melhor para o seu caso em particular :)

Espero que esse post tenha te ajudado e que possamos ter um mundo com mais testes, mais qualidade e mais pessoas dormindo tranquilamente sabendo que seu botão vermelho de bomba nuclear está devidamente coberto.

Abraço e até a próxima aula!

Ficou com dúvida ou quer mandar uma real? Deixa nos comentários!
Ah, e me acompanhe também lá no YouTube✌!

--

--

Ricardo Pedroni
RPEDRONI

O Professor Ricardo Pedroni ensina conceitos importantes e boas práticas de desenvolvimento de projetos em software. YouTube https://bit.ly/3q0TIAU