Como escrever testes rápidos
Autor: Vinicius Assef | Publicado: 2016-09-26T10:00:00-03:00



tl;dr
-----

Desenvolva uma arquitetura onde as partes sejam pequenas, fáceis de serem trocadas e tenham nomes que ajudam a entender seu comportamento.


O problema
----------

No artigo [Seu teste unitário precisa rodar em menos de 100ms](http://aprenda-python.blogspot.com/2016/06/seu-teste-unitario-deve-rodar-em-menos-de-100ms.html) eu citei algumas dicas para testes rodarem rápido, mas é a interação entre os componentes do seu software que vai fazer diferença na velocidade e na simplicidade (ou complexidade) dos testes.

Em outras palavras, se os testes são lentos ou complicados, a arquitetura está ruim. Quando isso acontece, o código de produção precisa ser re-estruturado (ou refatorado, como queira).

Eu costumo dizer que os testes precisam fazer parte da solução e não do problema. A grande questão é que sempre temos prazos a cumprir e, obviamente, escolhemos ferramentas (ou formas de usá-las) que encurtam o tempo de entrega. Elas nos trazem velocidade no curto prazo (eficácia), mas não necessariamente eficiência a médio/longo prazo.

Nesse cenário as vantagens e desvantagens da forma como desenvolvemos os testes -- e os sistemas -- precisam ser avaliadas. Se o projeto é pequeno, simples ou de curta duração, testes um pouco lentos e uma arquitetura menos elaborada não serão problema. Por outro lado, se o projeto é de média/longa duração, complexo ou grande, testes lentos serão um problema sério. Outro fator que influencia na avaliação da arquitetura é o nível de incerteza (ou de dúvidas) que existe no projeto. Vou exemplificar.

Provavelmente haverá poucas dúvidas sobre as regras de negócio de um gerenciador de blog. Os testes podem até ser lentos e uma arquitetura relativamente simples dá conta do recado. Mas para um sistema para controle tributário, por exemplo, o cenário é outro. Haverá muitas regras de negócio, várias fórmulas de cálculo personalizáveis, legislação que é aplicada em algumas situações e em outras, não. O prazo das adequações normalmente é ditado por lei e o descumprimento acarreta multas altas. Enfim, o sistema é claramente maior e muito mais complexo. Você terá muitas dúvidas e muitas variações de regra. Por isso a arquitetura do sistema precisa ser muito mais elaborada do que no exemplo do blog. Muitos componentes precisam interagir para entregar o resultado correto. Consequentemente seus testes precisam ser mais específicos e mais rápidos. Sua rede de proteção contra acidentes precisa ser maior e mais forte. Porque se eles forem lentos e você não rodá-los com a frequência necessária, estará ignorando a proteção.



Algumas dicas para melhorar a arquitetura do seu sistema e acelerar seus testes
-------------------------------------------------------------------------------

1) Proteja suas fronteiras.

Os países defendem suas fronteiras para evitar que elementos externos e não autorizados entrem em seu território e comprometam a soberania nacional. Você deve fazer o mesmo em seus sistemas.

Componentes e estruturas de dados de outros sistemas (ou bibliotecas) não deveriam poder circular sem um controle rígido. Só quem tiver "visto de entrada" pode frequentar seu sistema.

Em alguns casos as fronteiras são muito claras. Se eu preciso interagir com o Twitter, ele está fora das fronteiras do meu sistema, certo? E se eu precisar enviar um email? Isso também estaria fora dos limites do meu sistema? Alguns alegam que sim, outros alegam que não. Nesse caso eu voto pelo "sim". A não ser que eu esteja desenvolvendo um sistema de envio de emails.

A dica é: escreva uma camada de isolamento -- um [Adapter](https://en.wikipedia.org/wiki/Adapter_pattern) -- para os componentes que não fazem parte do seu sistema. Um benefício adicional é que você pode usar um dublê para rodar os testes, sem precisar da integração real (o Twitter ou o email, no exemplo acima).


2) Desenvolva coisas simples.

Um dos princípios constantes no [Zen do Python](http://www.thezenofpython.com) é:

> Simples é melhor do que complexo.

Não há quem não concorde com isso. A questão é que desenvolvemos software para resolver problemas complexos. Seria possível desenvolver coisas simples para resolver problemas complexos? Em software, a resposta é SIM.

Um sistema grande não precisa ser uma coisa só. Daí a origem da palavra "sistema". De acordo com a [Wikipedia](https://pt.wikipedia.org/wiki/Sistema):

> Um sistema (do grego σύστημα systēma, através do latim systēma), é um conjunto de elementos interdependentes de modo a formar um todo organizado.

Se o que você desenvolveu não é formado por "elementos interdependentes" ou não forma "um todo organizado", você não fez um sistema. Você fez uma confusão.


Em geral, coisas simples são pequenas. Um exemplo que gosto muito vem dos automóveis. Podemos ter carros originais de fábrica ou "tunados". Algumas partes podem ser trocadas por outras que atendem a uma "determinada especificação" (interface) e a coisa funciona. Às vezes detalhes impedem de usar alguns componentes. Por exemplo, rodas com 5 parafusos, por mais modernas que sejam, não encaixam em carros que esperam rodas com 4 parafusos.

Alguns componentes são mais fáceis de trocar. Outros, precisam de mais capacidade técnica. Ainda assim, várias peças internas podem ser trocadas, desde que atendam à especificação do fabricante. No caso do seu sistema, o fabricante é você.

Essas peças (componentes) pequenas dos automóveis foram projetadas e desenvolvidas para funcionarem dentro de determinado contexto. Software também deveria ser desenvolvido assim.

Um dos conceitos que reforçam esse pensamento na arquitetura de software é o [Single Responsibility Principle](https://en.wikipedia.org/wiki/Single_responsibility_principle). Infelizmente ferramentas que fazem sucesso entre nós, programadores, não seguem esse princípio que ajuda a simplificar os sistemas. Um exemplo é a forma como o ORM do Django incentiva a implementação dos Models e Managers, levando à síndrome que chamo de Fat Model Classes e que muitas pessoas chamam de _"Fat Models"_. Mas "Fat Models" deveria significar _"camada (e não classe) Model com regras de negócio e métodos relacionados ao negócio, divididos em vários componentes coesos"_; o que nos remete à definição de "sistema" que lemos no início dessa seção.

Pelo contrário, vemos em sistemas implementados usando Django as classes Model com muitos métodos e regras de negócio, auxiliadas por uma camada de serviço para acesso a dados, os Managers. Essa forma de organização -- que viola o Single Responsibility Principle -- pode virar um problema sério de integração quando dois programadores precisam mexer em diferentes partes da mesma classe. Afinal ela fica extensa.

Não é fácil decompor um sistema em partes simples. Exige disciplina e estudo constante, mas é mais simples entender pedaços pequenos. É mais simples certificar que coisas pequenas funcionam. É mais simples garantir que um componente pequeno se encaixe em outro, também pequeno. Eu ouvi a palavra "testes" aí?

Desenvolva coisas simples e pequenas.


3) Dê nomes significativos para o que você desenvolver.

Essa dica parece muito elementar. E é mesmo. Mas só dá para nomear adequadamente aquilo que você entende adequadamente.

Quem nunca se deparou com o famoso _"eu entendi mas não consigo explicar"_? Essa situação mostra que você **acha** que entendeu, mas ainda tem dúvidas a respeito; você não entendeu profundamente. O mesmo acontece quando não encontramos um bom nome para um componente, para um método ou para uma abstração.

Nomes são muito importantes. O nome é a descrição resumida de um objeto. Um nome claro indica um pensamento claro sobre o assunto.

Por exemplo, quando você vê um campo em uma tabela chamado `data`, o que você acha ele armazena? Uma data. Certo. Mas data de quê? Se o nome do campo for `data_exc`, o que significa? "data de exclusão" ou "data da excursão"? É claro que o contexto vai determinar o significado de muita coisa. A palavra "mangueira" significa uma árvore frutífera ou um utensílio que sai água? O contexto é quem determina o significado. Entretanto, não devemos deixar o contexto implícito.

Nomear "coisas" não é tarefa fácil, principalmente quando lidamos com abstrações. Abstrações, por si só, podem ser difíceis de entender. Dar nomes a elas pode parecer impossível. O treino e a prática vão determinar nosso nível de sucesso.

Um outro benefício de nomear corretamente os componentes, além de deixar seu comportamento ou finalidade explícitos, é que você terá facilidade para eliminar duplicidade. Às vezes comandos de apenas uma linha estão espalhados pelo código. Um comportamento escondido, que poderá ser modificado com o passar do tempo. Transformando-os em métodos ou em funções certamente vai ajudar a entender melhor seus programas e concentrar possíveis manutenções futuras nos métodos simples e pequenos que você criou.

Sempre que possível dê nomes claros aos componentes (variáveis, métodos, classes, pacotes, módulos, tabelas, campos...) para que outros programadores -- e você mesmo, no futuro -- consiga entender o quê e onde precisa mexer.



Conclusão
---------

A velocidade e a simplicidade dos seus testes estão diretamente relacionados à qualidade da arquitetura que você desenvolve. Por isso os especialistas afirmam que praticar [Test Driven Development (TDD)](https://en.wikipedia.org/wiki/Test-driven_development) tem como objetivo simplificar a arquitetura do software. E ainda traz, como benefício adicional, a garantia que todas as partes estão funcionando como deveriam.

Se os testes forem demorados você certamente irá abandoná-los mais cedo ou mais tarde. Desenvolva cuidadosamente a arquitetura de seu software e aproveite os testes automáticos.