If I have a fairly complex object with a complex method, and I write my test and the bare minimum to make it pass (after it first fails, Red). When do I go back and write the real code? And how much real code do I write before I retest? I'm guessing that last one is more intuition.
Você não "volta" e escreve "código real". É tudo código real. O que você faz é voltar e adicionar outro teste que force você a alterar seu código para fazer o novo teste passar.
Quanto de quanto você escreve código antes de testar novamente? Nenhum. Você escreve o código zero sem um teste de falha que força você a escrever mais código.
Observe o padrão?
Vamos analisar outro exemplo simples na esperança de que isso ajude.
Assert.Equal("1", FizzBuzz(1));
Fácil peazy.
public String FizzBuzz(int n) {
return 1.ToString();
}
Não é o que você chamaria de código real, certo? Vamos adicionar um teste que force uma mudança.
Assert.Equal("2", FizzBuzz(2));
Poderíamos fazer algo bobo como if n == 1
, mas vamos pular para a solução sensata.
public String FizzBuzz(int n) {
return n.ToString();
}
Legal. Isso funcionará para todos os números não-FizzBuzz. Qual é a próxima entrada que irá forçar o código de produção a mudar?
Assert.Equal("Fizz", FizzBuzz(3));
public String FizzBuzz(int n) {
if (n == 3)
return "Fizz";
return n.ToString();
}
E novamente. Escreva um teste que não passará ainda.
Assert.Equal("Fizz", FizzBuzz(6));
public String FizzBuzz(int n) {
if (n % 3 == 0)
return "Fizz";
return n.ToString();
}
E agora cobrimos todos os múltiplos de três (que também não são múltiplos de cinco, vamos anotar e voltar).
Ainda não escrevemos um teste para "Buzz", então vamos escrever isso.
Assert.Equal("Buzz", FizzBuzz(5));
public String FizzBuzz(int n) {
if (n % 3 == 0)
return "Fizz";
if (n == 5)
return "Buzz"
return n.ToString();
}
E novamente, sabemos que há outro caso que precisamos lidar.
Assert.Equal("Buzz", FizzBuzz(10));
public String FizzBuzz(int n) {
if (n % 3 == 0)
return "Fizz";
if (n % 5 == 0)
return "Buzz"
return n.ToString();
}
E agora podemos lidar com todos os múltiplos de 5 que também não são múltiplos de 3.
Até este ponto, ignoramos a etapa de refatoração, mas vejo alguma duplicação. Vamos limpar isso agora.
private bool isDivisibleBy(int divisor, int input) {
return (input % divisor == 0);
}
public String FizzBuzz(int n) {
if (isDivisibleBy(3, n))
return "Fizz";
if (isDivisibleBy(5, n))
return "Buzz"
return n.ToString();
}
Legal. Agora nós removemos a duplicação e criamos uma função bem nomeada. Qual é o próximo teste que podemos escrever que nos forçará a mudar o código? Bem, temos evitado o caso em que o número é divisível por ambos 3 e 5. Vamos escrever agora.
Assert.Equal("FizzBuzz", FizzBuzz(15));
public String FizzBuzz(int n) {
if (isDivisibleBy(3, n) && isDivisibleBy(5, n))
return "FizzBuzz";
if (isDivisibleBy(3, n))
return "Fizz";
if (isDivisibleBy(5, n))
return "Buzz"
return n.ToString();
}
Os testes passam, mas temos mais duplicação. Temos opções, mas vou aplicar "Extrair Variável Local" algumas vezes para refatorar em vez de reescrever.
public String FizzBuzz(int n) {
var isDivisibleBy3 = isDivisibleBy(3, n);
var isDivisibleBy5 = isDivisibleBy(5, n);
if ( isDivisibleBy3 && isDivisibleBy5 )
return "FizzBuzz";
if ( isDivisibleBy3 )
return "Fizz";
if ( isDivisibleBy5 )
return "Buzz"
return n.ToString();
}
E cobrimos todas as contribuições razoáveis, mas e a entrada não razoável ? O que acontece se passarmos 0 ou um negativo? Escreva esses casos de teste.
public String FizzBuzz(int n) {
if (n < 1)
throw new InvalidArgException("n must be >= 1);
var isDivisibleBy3 = isDivisibleBy(3, n);
var isDivisibleBy5 = isDivisibleBy(5, n);
if ( isDivisibleBy3 && isDivisibleBy5 )
return "FizzBuzz";
if ( isDivisibleBy3 )
return "Fizz";
if ( isDivisibleBy5 )
return "Buzz"
return n.ToString();
}
Isso está começando a parecer "código real" ainda? Mais importante, em que ponto deixou de ser "código irreal" e transição para ser "real"? Isso é algo para refletir sobre ...
Então, eu consegui fazer isso simplesmente procurando por um teste que eu sabia que não passaria em cada etapa, mas eu tive muita prática. Quando estou no trabalho, as coisas nem sempre são tão simples e nem sempre sei que teste vai forçar uma mudança. Às vezes, escrevo um teste e fico surpreso ao ver que ele já passa! Eu recomendo que você crie o hábito de criar uma "Lista de teste" antes de começar. Esta lista de testes deve conter todas as entradas "interessantes" que você pode imaginar. Você pode não usá-los todos e provavelmente adicionará casos, mas essa lista serve como um roteiro. Minha lista de testes para o FizzBuzz seria algo como isto.
- Negativo
- Zero
- Um
- Dois
- Três
- Quatro
- Cinco
- Seis (múltiplo não trivial de 3)
- nove (3 ao quadrado)
- Dez (múltiplo não trivial de 5)
- 15 (múltiplo de 3 e 5)
- 30 (múltiplo não trivial de 3 e 5)