Como não odiar os seus testes

Camila Campos
Creditas Tech
Published in
7 min readJun 26, 2019
Photo by T. Chick McClure on Unsplash

Nós odiamos nossos testes?

"Ódio" é um sentimento deveras forte, e quando eu uso um título como esse, eu estou fazendo (pelo menos) duas grandes suposições aqui:
1. Nós escrevemos testes automatizados (e portanto, sabemos de pelo menos de algumas das vantagens deles);
2. Nós odiamos nossos testes (mesmo que só alguns deles, mesmo que só de vez em quando).

Esse último ponto é BEM ousado, certo? Minha primeira reação ao pensar nesse suposto "ódio" é algo na linha de um "PFFF SE LIGA NADA A VER". Mas, até as maiores pessoas entusiastas de testes que eu conheço, em algum momento da vida, sentiram um sentimento ruim muito forte em relação a pelo menos um teste. Pode ser que esse teste nem tenha sido escrito pela própria pessoa e que ela só teve que olhar e tentar entender o que estava acontecendo por ali, mas independente disso, já rolou um sentimento de ódio por causa desse teste (ou testes).

Pra quem ainda não entendeu o sentimento, pense nas seguintes situações:

  • Quando você olha um teste e não entende absolutamente nada do que está acontecendo ali;
  • Quando você percebe que o teste não está testando absolutamente nada ou você tem certeza de que ele vai quebrar, mas mesmo assim ele passa;
  • Quando você abre um teste e a única vontade que você tem é de deletar ele inteirinho.

Esses e tantos outros momentos representam o ódio que temos por testes. E acredite em mim: se você nunca se sentiu assim, você irá!

Já que o “ódio” existe, já que ele é real, já que nós eventualmente odiamos nossos testes, por que, então, os escrevemos mesmo assim? Por que continuamos a escrevê-los, mesmo sabendo que em algum momento vamos acabar não gostando daquele(s) teste(s)?

Antes de tudo, é preciso entender por que escrevemos testes.

Se acabamos não gostando de nossos testes, por que os escrevemos mesmo assim? A resposta é muito simples: nós gostamos! Aprendemos ao longo da nossa carreira como pessoas desenvolvedoras que escrever testes é legal e, mais do que isso, que boas e bons devs escrevem testes (e são melhores devs ainda se os escrevem antes de escrever o código "de verdade"). Nós vimos na prática como escrever testes acaba melhorando o design e a qualidade do nosso código, e como acaba também diminuindo a incidência de bugs na nossa aplicação. Por conta disso, nós os escrevemos. Queremos estar do lado certo da história (risos), afinal.

Mas não é só isso, não é mesmo?! Não escrevemos testes só porque gostamos de escrevê-los e aprendemos que esse é o certo sem ter motivos claros. Nós escrevemos testes porque eles também garantem duas grandes coisas em nossas aplicações:

  1. Alta confiança no software que estamos entregando. Testes conseguem garantir que o que está sendo testado funciona, e que vai continuar funcionando quando adicionarmos alguma nova funcionalidade. Eles garantem também que alguém possa mudar um código existente sem quebrar nada, desde que o comportamento inicial não mude (aka refatoração).
  2. Entendimento do código que estamos escrevendo. Testes também ajudam a garantir que o código escrito está fácil de ser usado e modificado (alterando ou não o comportamento), além de guiar o design do nosso código.

Então por que odiamos nossos testes?

Entendemos que temos vantagens bem relevantes que nos levam a escrever testes para nossas aplicações. Porém, se essas vantagens são tão legais assim, por que nós acabamos não gostando de alguns (vários) de nossos testes?

Existem, entre outros, 3 grandes motivos pra esse ódio todo existir:

  1. Nossos testes são lentos demais: demoram tanto tempo pra rodar que nos dá preguiça de rodá-los;
  2. Nossos testes são muito complicados de serem feitos ou mantidos, e portanto não temos vontade de fazê-los (ou rodá-los);
  3. Nossos testes são desnecessários: por não sabermos exatamente o quê eles estão testando, ou por quê eles estão testando algo (que talvez já esteja coberto por outro teste).

A partir de agora, vamos começar a explorar cada um dos motivos acima, entendendo melhor porque eles são um problema e o que podemos fazer para evitá-los.

Evitando e se previnindo de testes muito lentos

Quando pensamos no tempo que nossos testes levam pra rodar em relação à quantidade de testes que temos, nós esperamos que quanto mais testes nós tenhamos, mais tempo eles levem pra rodar. Mas nós esperamos que essa curva cresça, linearmente (como mostra a linha verde da figura abaixo).

Expectativa em verde, representando crescimento linear de velocidade ao rodar testes); Realidade em vermelho, representando crescimento exponencial.

Só que na verdade, o que a gente costuma ver é uma linha de crescimento exponencial (como a linha vermelha ao lado). Quanto mais testes nós temos, mais cada teste leva pra rodar.

Por exemplo, se temos uma aplicação com 10 testes, que rodam em 10 segundos, esperamos que quando tivermos 100 testes, eles rodem em 100 segundos, mas o que acontece é que ele passam a rodar em 1000 segundos (posso exagerar um pouco, mas a licença poética permite).

A pirâmide de testes

Pirâmide de testes

Muitos dos problemas de lentidão tem origem no (não) entendimento da pirâmide de testes.

A função dela é basicamente definir níveis de testes e quantos testes você deveria ter em cada nível.

Você pode ler mais sobre ela nesse outro post que fiz.

Na base da pirâmide, temos testes (teoricamente) super simples e rápidos: os testes de unidade. Eles compõem a maior parte dos nossos testes. No meio, temos os testes de integração, que são mais complexos e demorados que os testes de unidade. Estes geralmente são testes de endpoints ou features específicas, que compões algumas unidades, mas não testam o funcionamento do software como um todo. Na ponta, temos os testes de ponta a ponta: eles são bem mais complexos e demorados, pois cobrem fluxos inteiros da aplicação, portanto geralmente temos bem poucos desses.

Legal, mas onde mora o problema?

Uma visão mais realista da pirâmide de testes, sem bordas claras entre cada nível de teste, e com testes que pertencem a mais de um nível.

O problema é que muitas vezes temos bem pouco entendimento do que são cada um desses níveis ou de como eles se comportam. Por isso, nossa pirâmide de testes acaba ficando bagunçada, sem divisão e diferenças claras entre os tipos de testes, ou ainda com testes que podem pertencer a dois ou mais níveis. Isso implica em testes que precisam ser todos rodados a cada pequena modificação, e em testes bem mais lentos do que poderiam ser.

Minha dica para resolver esse tipo de problema, é sempre pensar primeiro nos seus testes de unidade. Eles são menores e simples, e devem ser a maior parte da sua base. Depois, pense nos fluxos mais críticos da sua aplicação; pense em no máximo 20 casos (total). Para esses, escreva testes de ponta a ponta. Por último, escreva testes de integração para todas as demais funcionalidades da sua aplicação (pode escrever para casos já cobertos pelo ponta a ponta também).

Outra dica é escrever cada um desses níveis de testes em pastas separadas, e rodá-los somente conforme necessário. Por exemplo, sempre que mudar uma classe, rode o teste de unidade dela. Ao finalizar uma série de modificações, rode todos os testes de integração (em especial, o da feature que você acabou de modificar). Assim que estiver com o código OK, pronto para subir para produção, rode todos os testes de ponta a ponta. Com isso, você otimiza seu tempo, rodando os testes mais demorados bem menos vezes.

É também muito importante definir regras sobre como os testes da sua aplicação tratarão qualquer tipo de dependências. Como será o acesso a outras classes? E a dependências externas, como outros serviços? Seu teste deve ou não acessar o banco de dados?

Na Creditas, nós definimos as regras da seguinte forma:

  • Os testes de unidade não acessam absolutamente nada de fora do que está sendo testado: sem banco de dados, sem dependências externas, sem outras classes/objetos. Para que consigamos fazer isso, usamos muita injeção de dependência!
  • Os testes de integração acessam um banco de dados criado na hora do teste e deletado assim que eles acabam. Acessam também demais classes colaboradoras, mas não acessam nenhuma dependência externa.
  • Os testes de ponta a ponta acessam tudo que tem direito: banco de dados, dependências externas, classes e objetos utilizados durante o fluxo testado. A única restrição que temos é tentar usar ambientes de staging ou homologação desses serviços externos (caso não exista algo assim, criamos um próprio nosso, para não acessar a dependência externa “de verdade”.
Tabela representando as regras e convenções sobre como a Creditas lida com dependências de cada nível de teste.

Sobre testes muito complicados ou desnecessários

Para que esse texto não fique gigante, decidi separar em uma série de artigos. O primeiro da série (este que você está lendo) cobre uma introdução sobre motivos que nos fazem ficar tristes com nossos testes e mostra soluções para testes muito lentos.

Para entender melhor sobre testes muito complicados e testes desnecessários, aguarde os próximos capítulos ❤ Me acompanhe no twitter que postarei novidades lá!

Mal posso esperar!

Essa série de posts é um compilado do que foi falado em palestras dadas na RubyConfBR 2017, TDC São Paulo e Florianópolis 2018 e The Conf 2018.

Tem interesse em trabalhar conosco? Nós estamos sempre procurando por pessoas apaixonadas por tecnologia para fazer parte da nossa tripulação! Você pode conferir nossas vagas aqui.

--

--

Camila Campos
Creditas Tech

Uma dev doida, apaixonada por testes e qualidade de código, que trabalha na SumUp e que quer incluir mais mulheres na computação através do Rails Girls SP.