Após iniciar um texto sobre a importância de testes automatizados para código de definição de infraestrutura, refleti sobre o uso de TDD nesses casos e cheguei a uma conclusão um pouco polêmica.
Queria deixar claro que esse texto é propositivo e aberto a mudança de opiniões com base nas interações. Meu objetivo é iniciar esse debate, apresentando as minhas conclusões sobre esse assunto e que, assim, possamos juntos concluir algo sobre isso.
Antes de pular para conclusão, vamos primeiro contextualizar o que é TDD. De acordo com o nosso “nerd alfa” Martin Fowler:
“Test-Driven Development (TDD) é uma técnica para construção de software que o processo de desenvolvimento através da escrita de testes.”
De acordo com esse artigo, é uma prática que força o desenvolvedor a pensar sobre seu código antes de o escrever e que TDD é muito mais sobre design do que teste (dica do Luis, nos comentários na postagem no meu blog).
Em essência, você segue dois simples passos repetidamente:
- Escreva um teste para validar a funcionalidade que precisa adicionar – Exemplo: quero uma função para somar dois números, logo, eu escrevo um teste em que a soma de 2 + 2 o tenha sempre 4 como resultado. Qualquer resultado diferente disso, indica que a função está errada;
- Escreva de forma imperativa o código até que ele passe nos testes – Exemplo: você escreve o código que recebe dois número como parâmetros e soma.
Seguindo essa lógica, é fácil pensar que seria ideal isso acontecer também para infraestrutura, certo? Não necessariamente.
Código de infra geralmente é descritivo e não imperativo
Nos exemplos demonstrados acima, temos, a partir de um único teste, uma grande gama de possibilidade de implementação da função a ser testada, ou seja, para um teste de soma de dois números, o código resultante para satisfazer essa validação pode variar bastante.
Desenvolvimento de software normalmente utiliza o modelo imperativo de construção, ou seja, você precisa dizer detalhadamente de que forma você deseja que seja feito. O código de infraestrutura regularmente segue uma outra linha, que é a descritiva. Você apenas precisa dizer o que deseja e não precisa implementar como isso será executado.
Porque não TDD
Levando em consideração que um dos motivadores do TDD seja o auxilio na construção da lógica por trás da solução do teste em questão e no modelo descritivo a lógica é algo raro, fazer um teste para cada definição de infraestrutura me parece um trabalho desnecessário, já que o código de teste seria bem parecido com o usado para atender o teste.
Exemplo: Quero criar o usuário elmo e o teste seria criado da seguinte maneira com o serverspec:
describe user('elmo') do it { should exist } end
O código de infraestrutura para atender o teste acima seria:
user { 'elmo': ensure => present, }
Você há de concordar que esse teste nunca falharia, uma vez que não precisei de nenhuma lógica aqui. Sem falar que eles são bem parecidos na sua escrita, ou seja, no final das contas, seria um grande desperdício de tempo.
E se meu código tiver lógica, faz sentido TDD?
Tecnicamente falando, sim; mas na prática, nem toda definição terá lógica e ter isso como metodologia de desenvolvimento talvez atrapalhe mais do que gere algum valor para seu processo de desenvolvimento.
Isso quer dizer que não devo ter teste no meu código?
Claro que não! Testes automatizados são bem importantes para a confiança do seu código. Não vou me aprofundar nas razões de ser fazer teste, pois isso foi bem explicado aqui.
Depois de alguns comentários…
Este artigo foi publicado inicialmente no meu blog e gerou uma boa interação nos comentários. Depois de lê-los e conversar com meus leitores, vale a pena deixar claro algumas coisas que talvez tenham ficado meio solta no artigo e adicionar outras que faltaram no texto ou estavam de forma incorreta. Veja abaixo:
TDD somente com teste unitário?
A ideia que é TDD seja orientado a teste unitário não foi proposital, mas no caso de teste de infraestrutura esqueci de comentar que testes de integração e outros mais complexos demandam interação com uma infraestrutura provisionada, ou seja, o feedback é bem lento, o que inviabiliza (em minha opinião) o trabalho orientado a teste.
É importante que você crie seu ambiente “do zero” a cada teste, para que você garanta que não tem dado de outro teste ou alguma modificação fora da definição no ambiente. Com isso em mente, é comum encontrar situações que podem demorar até 30 minutos para reconstruir o ambiente, aplicar as definições e então executar os testes.
Se você for utilizar testes de integração ou outros mais complexos pra guiar seu processo de desenvolvimento, ter esse tempo de espera a cada interação “Red-Green-Refactor” seria complicado para o processo de escrita de código.