Somos defensores do uso de TDD, mas entendemos que essa escolha, como
qualquer outra, deve ser consciente, e não fruto de empolgação com
modismos. Após avaliarmos e constatarmos os benefícios de construir
software com TDD, e nos convencermos de que vale a pena desenvolver
deste jeito em nossas empresas, precisamos lembrar que estamos lidando
com pessoas.
Desenvolver com TDD não é uma questão de seguir um processo
padrão da empresa, imposto de cima para baixo, nem meramente cobrar
indicadores de qualidade, como certos níveis de cobertura de código.
Antes de tudo, implantar TDD é comunicar valores e incutir princípios.
Precisamos trabalhar com os desenvolvedores através de palestras,
treinos (Dojo é uma excelente técnica), e outras atividades fora do
contexto das tarefas cotidianas, visando não apenas mostrar como
funciona a técnica, mas principalmente para reforçar seus benefícios.
O princípio mais fundamental do TDD é “nenhum código
funcional deve ser escrito se não for ajudar a fazer passar um teste que
está dando vermelho”. Por mais simples que possa parecer,
quando vamos construir algum método, estamos acostumados a implementar o
máximo de detalhes que conseguimos pensar sem perder o controle do que
estávamos construindo quando começamos a codificar tal método.
Imagine um método que calcule a quantidade de meses entre duas datas,
sendo que o primeiro mês só deve ser contado se o dia da primeira data
for anterior ao dia 15. Esse método só deve retornar valores maiores ou
iguais que zero. Note que todos os exemplos que utilizaremos foram
extraídos de um sistema real. Digamos que o primeiro caso de teste construído seja o seguinte:
[Test]
public void ContarQuantidadeDeMeses_DataBaseAnteriorAoDia15()
{
var dataBaseReajuste = new DateTime(2010, 1, 1);
var mesPatrimonial = new DateTime(2010, 6, 1);
var geradorDeArrecadacoes = new GeradorDeArrecadacoes();
var quantidadeDeMeses =
geradorDeArrecadacoes.ContarNumeroDeMesesParaReajuste(
mesPatrimonial,
dataBaseReajuste);
Assert.AreEqual(5, quantidadeDeMeses);
}
Nós, quando estamos começando com TDD, ao implementarmos o método
ContarNumeroDeMesesParaReajuste() visando fazer este primeiro teste
passar, somos fortemente tentados a escrever mais código do que
deveríamos, quebrando o princípio fundamental de que “nenhum código
funcional deve ser escrito se não for ajudar a fazer passar um teste que
está dando vermelho”.
Digamos que o método seja construído ainda vazio, apenas para fazer o
teste dar vermelho, da seguinte forma:
public virtual int ContarNumeroDeMesesParaReajuste(
DateTime? pMesPatrimonial,
DateTime? pDataBase)
{
throw new NotImplementedException();
}
Ao iniciarmos sua construção, visando fazer o teste passar, já nos
deparamos com o fato dos parâmetros serem nuláveis, e somos tentados a
tratar o caso do parâmetro passado ser nulo. Também somos tentados a já
fazer a verificação se o dia da “data base” é menor que 15. Porém esses
dois comportamentos, se construídos, não estarão cobertos pelo teste
que está vermelho.
Uma implementação boa o suficiente para fazer o teste dar verde
seria:
public virtual int ContarNumeroDeMesesParaReajuste(
DateTime? pMesPatrimonial,
DateTime? pDataBase)
{
return pMesPatrimonial.Value.Month - pDataBase.Value.Month;
}
Contudo, nossa forma de pensar pré-TDD seria resolver o máximo de
coisas durante a codificação. Provavelmente nós desenvolveríamos algo
como:
public virtual int ContarNumeroDeMesesParaReajuste(
DateTime? pMesPatrimonial,
DateTime? pDataBase)
{
if (pMesPatrimonial == null)
throw new ArgumentNullException("pMesPatrimonial");
if (pDataBase == null)
throw new ArgumentNullException("pDataBase");
var quantidadeDeMeses =
pMesPatrimonial.Value.Month - pDataBase.Value.Month;
if (pDataBase.Value.Day >= 15)
quantidadeDeMeses -= 1;
return quantidadeDeMeses;
}
Logo, mesmo em métodos simples como este, nossa implementação
exigiria ao menos 5 casos de teste, que cobrissem os seguintes fluxos:
- Finalizar com exceção por pMesPatrimonial ser nulo
- Finalizar com exceção por pDataBase ser nulo
- Retornar a quantidade de meses sem descontar o primeiro mês (Dia <
15) - Retornar a quantidade de meses descontando o primeiro mês (Dia ==
15) - Retornar a quantidade de meses descontando o primeiro mês (Dia >
15)
Note que “>=” é uma forma simplificada de representar duas
condições, maior ou igual, e as duas devem ser testadas. É
impressionante como alguns desenvolvedores acham esse processo um
exagero, um excesso de rigor. Mas em pequenas coisas como essas, bugs
sérios podem nascer. Se teremos de testar todos os cenários (ou não
teremos?) que ganho pode haver em implementar em uma única tacada um
método com 5 fluxos de execução, sendo que o teste atual irá executar
somente um deles?
Essa atitude, falsamente motivada pela “maturidade”
(ou praticidade) do desenvolvedor, não possui qualquer justificativa
aceitável. Na verdade, é mais um vício. Uma dependência de uma forma de
pensar. Uma dificuldade em disciplinar a mente e conter o impulso de
sair codificando mais do que o necessário. É uma dificuldade em
compreender o TDD. Desenvolvedores experientes também cometem erros
bobos. Ponto!
A disciplina do TDD nos faz dar cada passo de forma muito segura e consciente. Disciplinar nossa mente e trabalhar por princípios implica
em realmente assumir que não
existe um cenário, por mais simples que seja, que justifique a quebra de
um princípio fundamental com o qual trabalhamos.
Sem disciplina não há TDD, pois boa parte dos benefícios da
técnica está exatamente no trabalhar disciplinadamente.
TDD exige que analisemos com calma cada problema antes de
implementá-lo. Devemos pensar primeiramente na lista de testes, e só
depois iniciar a construção das funcionalidades, começando pelo teste, e
dando um passo de cada vez. Ao longo do trabalho, sempre que
identificamos um novo cenário, apenas o acrescentamos à lista de testes,
sem desviar o foco do que estamos construindo, garantindo assim um
ritmo de trabalho muito mais fluido e produtivo, sem muitas trocas de
contexto.
No mundo real, mesmo métodos simples como este do nosso exemplo podem
conter certas regras de negócio que precisam ser consideradas. Por
exemplo, ainda precisaríamos considerar outros fluxos possíveis para
realmente concluirmos esta implementação:
- Finalizar com exceção se a data inicial for maior que a final
- Finalizar com exceção se a data inicial não for do mesmo ano que a
data final (sem entrar em detalhes, isso é uma regra do negócio) - Finalizar com exceção se a data inicial for posterior ao dia 15, e
do mesmo mês que a data final. Por exemplo, início=15/06/2010 e
final=16/06/2010 iria retornar “-1”, pois a diferença dos meses é zero, e
seria subtraído 1 por conta de ser posterior ao dia 15.
Concluindo, trabalhar com TDD é apostar em princípios. É, através de
uma boa dose de disciplina, reeducar a mente, pois construir
primeiramente o teste é tão simples que muitos têm dificuldade em
respeitar.