Em tempos onde discussões envolvendo a relevância dos testes têm causado furor nos diferentes campos da área do desenvolvimento de software, nada mais justo do que "chovermos no molhado", e falarmos sobre testes, mocks, stubs e todos os seus primos menos famosos.
Nessa primeira parte, vamos conceituar toda essa "sopa de letrinhas" e detalhar a forma peculiar como os testes são tratados dentro de um dos times da Globo.com.
Eu gosto muito da linha de pensamento que colegas como Rafael Martins e Rômulo Jales, têm em relação a testes automatizados: Pare de perder tempo separando-os em unitários, funcionais, aceitação ou regressão, e vá escrevê-los.
Particularmente, acho vantajoso praticar o TDD, deixando com que os testes me ajudem a montar a arquitetura da aplicação. Quando roubamos um pouco da filosofia do parágrafo anterior, temos um foco maior na resolução de um requisito do que na "minúcia" de uma classe/módulo/pacote. Organicamente conseguimos atingir um desenvolvimento focado no funcional, muito próximo de propostas como a do BDD, sem precisar recorrer a artifícios como user stories.
Ok... deixamos de ser tão "unitários", e aprendemos a lidar com o todo. Mas e os acessos ao banco de dados? Ao Memcached ou Redis? E as interações entre meus serviços e APIs internas? E as escritas em disco? E as sessões do usuário?
Como lidar com tudo isso?
O Esportes 1 é um dos 3 times que compõe o núcleo de desenvolvimento da área de esportes, da Globo.com.
No time, decidimos que a maioria dessas interações deve estar lá em tempo de execução dos testes. Ou seja, (com uma ajudinha do Django) nossos testes consultam o banco de dados, interagem com a camada de cache, escrevem em disco, etc.
Somos tão preguiçosos que os testes chegam a consultar os ambientes de teste/desenvolvimento dos serviços criados por outros times, como dados semânticos, de dados esportivos, de vídeos, etc.
Obviamente temos o trade-off dos tempos de execução, que até o momento não chegaram a ser um problema tão grande para a equipe.
Concluímos que nossos testes não são nem de longe isolados. Estamos mais preocupados em validar o comportamento de uma funcionalidade do que de um objeto em específico.
Batemos no peito, orgulhosos de nossa decisão, e começamos a encarar o mundo com olhos marejados pelo árduo caminho que percorremos para chegar até aqui.
Mas existe a "borda" da aplicação. Dali para fora, você não tem controle algum sobre o que acontece. Você apenas tem a promessa de que aquela API vai funcionar. Deixar com que os testes batam nesses serviços "estrangeiros" pode custar muito tempo (e claro, dinheiro), tornando inviável a execução dos mesmos em tempo de desenvolvimento.
E é aí meu amigo, depois dessa justificativa gigante, que entram os mocks, stubs, spies, etc.
Vamos abrir um parênteses aqui para esclarecer o conceito de "mock". Na documentação da biblioteca de mock do Python, temos a seguinte (e incrivelmente sucinta) definição:
A “mock object” is an object used to replace a real one in a system under test.
Ou, nas palavras de Martin Fowler:
(...) objetos pré-programados com informações que formam uma especificação das chamadas que esperam receber.
O termo mock faz parte de um conceito mais genérico, conhecido por Test Double. Segundo Fowler, esse termo é usado para qualquer objeto que tem por finalidade, em um teste, substituir um objeto real.
Logo, quando em Esportes1 consumimos um recurso externo, "mentimos" para o sistema. Além do Mock, existem outros dublês que nos ajudam nessa tarefa:
O InfoQ possui um artigo do Martin Fowler, traduzido para o Português, onde ele conceitua bem os diferentes tipos de dublês, principalmente o mock e stub. Vale a leitura.
Na segunda parte deste post, utilizaremos cada conceito de forma prática, e discutiremos um pouco mais sobre o grande dilema: Mockar ou não mockar.
Até a próxima.