Página Inicial > TDD > Não escreva código novo sem antes ter um teste falhando

Não escreva código novo sem antes ter um teste falhando

O título desse post é uma frase de Kent Beck, autor do livro Test Driven Development: By Example. A idéia é que você sempre escreva testes antes de implementar qualquer código. Após o teste escrito falhar, você implementa o suficiente para fazer o teste passar. Com os testes passando, você está livre para refatorar (tanto implementação, quanto teste). A partir daí você cria um novo teste e segue o mesmo fluxo. Esse ciclo se repete até você ter toda a funcionalidade deseja implementada, ou seja, ter testes para todas as possibilidades da sua implementação.

Este é o “bê-a-bá” de TDD, mas na prática isso dificilmente acontece. Não porque não queremos fazer testes (se você não quiser escrever testes, o problema é todo seu), mas porque somos exímios programadores, desenvolvemos orientados a testes por anos, e não precisamos mais seguir os baby steps (passos de bebê), afinal somos programadores maduros.

Sendo assim, pulamos etapas: codificamos primeiro para depois escrever os testes, refatoramos mesmo com testes ainda não passando, escrevemos mais testes mesmo tendo testes anteriores falhando, e por aí vai.

Cuidado! Por mais que você seja um programador “fodão”, ainda sim você pode deixar de testar alguma coisa. Uma lógica de negócio, uma alternativa de fluxo ou uma condição de erro podem passar desapercebidas ao se pular as etapas básicas de TDD. Esse teste faltando, por mais simples que seja, pode causar um erro em ambiente de produção e causar transtornos para o cliente e/ou usuário final da sua aplicação.

Vamos utilizar como exemplo uma simulação de pareamento, onde uma dupla de desenvolvedores irá criar um método chamado positive_balance? para dizer se uma conta bancária, representada pela classe BankAccount, possui saldo positivo.

A linguagem utilizada será Ruby e o framework para testes será RSpec.

Os programadores são Félix (piloto do pareamento) e Péricles. Os dois concordam em iniciar criando a classe BankAccount com a declaração do método positive_balance? sem nenhuma implementação:

class BankAccount
  def positive_balance?

  end
end

- Legal, agora vamos escrever nosso teste. - diz Péricles.
- Para uma conta bancária possuir fundos é nessário que seu saldo seja maior que zero.

describe BankAccount do
  it "should have positive balance" do
    account = BankAccount.new
    account.value = 100.00
    account.positive_balance?.should be_true
  end
end

Eles rodam o teste:

F

1)
NoMethodError in 'BankAccount should have positive balance'
undefined method `value=' for #
./spec/bank_account_spec.rb:6:

Finished in 0.010015 seconds

1 example, 1 failure

E o resultado com erro diz a eles que não existe um atributo value na classe BankAccount. Félix e Péricles o criam:

class BankAccount
  attr_accessor :value

  def positive_balance?

  end
end

E executam o teste novamente:

F

1)
'BankAccount should have positive balance' FAILED
expected nil to be true
./spec/bank_account_spec.rb:7:

Finished in 0.010605 seconds

1 example, 1 failure

O teste falha. Então chegou a hora de escrever código novo, a implementação da funcionalidade que eles querem. Félix implementa o suficiente para o teste passar.

class BankAccount
  attr_accessor :value

  def positive_balance?
    true
  end
end

Péricles discorda totalmente.
- Cê tá louco, mano?! Vai retornar true para tudo?! O cara vai ter sempre saldo na conta?

Félix argumenta.
- A gente não precisa escrever código suficiente para o teste passar? Isso é suficiente.

E roda o teste:

.

Finished in 0.009987 seconds

1 example, 0 failures

- Viu? Passou. - finaliza Félix.
- Mas isso é muito baby step. - reclama Péricles - Vamos implementar o código real, ou seja:

class BankAccount
  attr_accessor :value

  def positive_balance?
    self.value > 0
  end
end

- Mas por que vamos implementar isso agora? Afinal nossos testes estão passando. - Félix rebate.
- Porque está na cara que esse código retornando true sempre não funciona.

Félix continua forçando a discussão.
- Como não funciona? Funciona sim, os testes estão passando.
- Funciona, mas a implementação está errada. - diz Péricles.
- Errada? Mas atende os requisitos até o momento. Afinal, os testes são para assegurar que a lógica do negócio está sendo cumprida.

Péricles fica pensativo.
- Mas o único teste que fizemos não está cobrindo todos os casos da lógica.
- Concordo com você, Péricles. E o que devemos fazer agora então?
- Devemos escrever um teste em que a conta bancária não irá ter fundos.
- Exatamente! - confirma Félix.

E eles continuam nesse linha de raciocínio até o final do pareamento.

Não estou aqui dizendo que você tem que sempre seguir à risca o Red Green Refactor do TDD, muito menos usar baby steps toda vez que você codificar (afinal, a vida não é um dojo), mas que você tenha atenção e controle do que está fazendo, tento o domínio da funcionalidade que está implementando.

Uma das maneiras de se conseguir isso é com pareamento. Seu par irá lhe ajudar a não deixar escapar nenhum teste. Outra maneira é com inspeção de código. De repente, outro desenvolvedor que não participou da implementação pode enxergar algo que você (e/ou seu par) não viu.

De qualquer forma, seja humilde. Use as etapas de TDD para funcionalidades ou lógica mais complexas. E também fique livre para burlar as regras para implementar coisas simples e funcionalidades básicas, ou quando estiver bastante à vontade e seguro do que está fazendo. Mas nunca, eu disse nunca, deixe de escrever os testes.


TDD , , ,