Testes? Para quê?

--

Como desenvolvedor você provavelmente já se deparou com a seguinte situação: pedem pra você alterar a funcionalidade X, você faz isso, valida que ficou certo e segue adiante. Porém, no dia seguinte, alguém diz que a funcionalidade Y parou de funcionar depois da sua alteração. E era de uma área completamente diferente! Como isso é possível?! Infelizmente, no mundo de desenvolvimento isso é mais comum do que deveria.

Mas não seria ótimo se houvesse formas de evitar incidentes como esse? Aí você perderia menos tempo consertando coisas que quebraram acidentalmente e passaria mais tempo fazendo coisas que realmente agregassem valor ao seu projeto. Pois bem, existem sim maneiras de prevenir os chamados side-effects. Sabe qual é uma delas? Desenvolver testes para o seu projeto!

Legal, mas o que seriam esses testes?

Quando programamos geralmente criamos componentes que recebem algum tipo de entrada e executam operações sobre essa entrada para obter algum resultado. Por exemplo, um toque em um botão seria uma entrada para ir para outra tela, uma notificação seria a entrada para ativar um recurso de um aplicativo e assim por adiante.

Nesse contexto, os testes são rotinas que verificam que, dada uma entrada, a aplicação irá se comportar conforme espero e produzirá um resultado correto. Um teste de uma função de somas validaria que o retorno possui um valor correto. Um teste de inserção de um dado em um banco de dados validaria que as informações salvas sejam consistentes. Um teste de uma função que altera o layout de uma tela validaria que os componentes renderizados estariam com os layouts certos.

Entendi. E quais tipos de testes existem?

Existem 3 tipos de testes: testes unitários, testes funcionais/integração e testes de interface de usuário. Cada tipo verifica componentes em diferentes níveis de abstração e com diferentes complexidades.

Testes Unitários

Os testes unitários validam os componentes básicos do seu projeto, como funções, classes e interfaces, de maneira direta e isolada.

Nesse tipo de teste você não precisa focar se os componentes são utilizados de maneira correta e sim se o componente por si só cumpre o que promete.

Testes funcionais ou testes de integração

Testes de integração validam que o uso interligado dos diferentes componentes produz o resultado esperado.

Por exemplo, se o seu projeto consome dados de um web service teríamos um teste para garantir que a conexão com esse web service é efetuada com sucesso e os dados obtidos são tratados como o esperado. Da mesma forma, se salvamos modelos em um banco de dados local, um teste de integração validaria que a persistência de dados funciona.

Testes de interface de usuário

Os testes de interface de usuário automatizam o uso da sua aplicação em seus cenários reais de uso.

Por exemplo, um teste pode simular todo o fluxo de cadastro de um aplicativo, simulando a entrada de dados e ações que os usuários normalmente fazem.

São os testes com maior nível de abstração e maior custo de desenvolvimento, porém garantem que a sua aplicação realmente está funcionando para o usuário.

Ok, mas como eu monto um teste?

A forma como que um teste é desenvolvido depende muito de sua plataforma, complexidade do componente a ser testado e suas dependências.

Porém, de maneira geral, um teste passa por 3 etapas:

  1. A primeira etapa consiste em preparar as entradas necessárias para testar o componente. Nela criamos objetos os objetos necessários, atribuímos os valores iniciais às variáveis, etc.
  2. Na segunda etapa executamos a função do componente em si.
  3. Por fim, validamos que, depois que o componente processou as entradas, o componente se comportou corretamente e retornou um resultado esperado. Se sim, o teste passou. Caso contrário, o teste falhou.

Ok. Vamos botar a a mão na massa? Que tal montarmos testes para essa função que calcula números de Fibonacci?

A função tem 2 casos bases: o número de Fibonacci de 0 é 1 e o número de Fibonacci de 1 é 1. Para os demais casos o número de Fibonacci é obtido pelos números anteriores calculados recursivamente até chegarem nos casos bases.

Para a função ter uma cobertura de testes completa, ou seja, para todas as possibilidades tratadas pela função serem validadas por pelo menos um teste, podemos montar 3 testes: um para validar que o número de Fibonacci de 0 é 1, um para validar que o número de Fibonacci de 1 é 1, e, por fim, um para validar que um outro número de Fibonacci, por exemplo, o número 4, é calculado corretamente.

Note também que a função só aceita números inteiros positivos então não precisaremos testar a função com um valor negativo como parâmetro de entrada.

Começamos definindo as entradas para cada teste:

Agora chamamos a função para calcular os fatoriais e armazenamos os resultados em variáveis:

E depois comparamos as variáveis com os resultados esperados:

Por fim, deixando tudo de forma mais sucinta:

Pronto! Com os testes passando podemos usar essa função nos códigos de produção com a garantia que não teremos problemas.

Tá, mas é sempre fácil assim?

Para coisas simples, sim. Mas duvido que em seu projeto você vai simplesmente fazer uma função de Fibonacci. Então, quando fizer algo complexo e sentir que está difícil de testar, pense nessas duas dicas:

  • Divida componentes complexos em componentes menores e especializados. É muito mais fácil testar algo menor e com apenas um propósito.
  • Priorize interfaces ou protocolos do que componentes concretos. Assim você pode criar um componente para uso em produção e outro componente com um comportamento simplificado para os testes.

Outras dicas boas que se aplicam tanto para testes simples quanto complexos são:

  • Certifique-se de cada teste está realmente isolado. Um teste não pode ser afetado por outro.
  • Teste apenas os métodos públicos. Um método privado provavelmente será chamado por outro método público.
  • Pesquise sobre mocks e stubs. Eles são tipos de objetos especiais de objetos feitos especialmente para testes.
  • Não se preocupe em testar toda a sua base de código. Foque nas funções mais críticas e complexas.

Nossa, quanta coisa! Vale a pena mesmo tanto trabalho?

Desenvolver testes é uma tarefa trabalhosa. Você vai gastar tempo não só implementando as funcionalidades como também nos testes dessas funcionalidades.

E tempo é dinheiro né? Então, fazer testes só vai aumentar o custo do meu projeto, certo?

Errado. Os testes vão garantir que o seu produto funciona mesmo após mudanças. Eles garantem que o seu projeto cumpre sua especificação e evitam futuros problemas de regressão, conforme visto aqui.

Portanto, o custo dos testes gera na verdade um ganho, já que você irá gastar menos tempo resolvendo problemas que aconteceram depois do lançamento do seu produto.

Ok. Você me convenceu! Como eu aplico testes no meu processo de desenvolvimento?

Uma forma de integrar testes durante o desenvolvimento é utilizar a prática de TDD (Test Driven Development).

Para aplicá-lo basta executar o seguinte ciclo:

  1. Escreva um teste que falha.
  2. Mude o seu código até que o teste passe.
  3. Melhore o seu código refatorando-o.
  4. Repita o processo.

A partir do TDD também surgiu uma metodologia de testes chamada BDD (Behavior Driven Development).

O BDD segue os mesmos princípios que o TDD com a diferença que esses testes não focam em aspectos técnicos mas sim em cenários de uso do seu produto.

Esses cenários são descritos em uma linguagem ubíqua. Algo do tipo: "o meu app deve permitir o login via Facebook" ou "se o usuário não possuir saldo ele não pode seguir para o fluxo X"). Com esse tipo de linguagem pessoas sem uma base técnica também podem participar na criação dos testes.

De qualquer forma, ambas metodologias promovem o desenvolvimento contínuo dos testes. Dessa forma, ao chegar no primeiro entregável do seu projeto ele já estará todo testado e validado.

Agora que você chegou até aqui e sabe o básico sobre os testes, pense com carinho quando for fazer aquele projeto novo ou evoluir um projeto existente e considere aplicar testes já no desenvolvimento. No futuro você verá que foi uma boa decisão.

Sou o André. Um desenvolvedor mobile apaixonado por tecnologia. Para qualquer dúvida ou sugestão, estou por aqui.

--

--