Como o bebê é seu bebê passos em TDD?

36

Hoje estávamos treinando o TDD e descobrimos o seguinte ponto de mal-entendido.

A tarefa é para a entrada "1,2" return sum dos números que é 3. O que eu escrevi (em C #) foi:

numbers = input.Split(',');
return int.Parse(numbers[0]) + int.Parse(numbers[1]); //task said we have two numbers and input is correct

Mas outros caras preferiram fazer isso de outra maneira. Primeiro, para a entrada "1,2", eles adicionaram o seguinte código:

if (input == "1,2")
   return 3;

Em seguida, eles introduziram mais um teste para a entrada "4,5" e alteraram a implementação:

if (input == "1,2")
   return 3;
else if (input == "4,5")
   return 9;

E depois disso eles disseram "Ok, agora vemos o padrão" e implementamos o que eu inicialmente fiz.

Eu acho que a segunda abordagem melhor se encaixa na definição de TDD, mas ... devemos ser tão rigorosos sobre isso? Para mim, não há problema em pular passos triviais do bebê e combiná-los em "twinsteps" se eu tiver certeza de que não vou pular nada. Estou errado?

Atualizar. Eu cometi um erro ao não esclarecer que não foi o primeiro teste. Já havia alguns testes, então "return 3" na verdade não era a parte mais simples do código para satisfazer o requisito.

    
por SiberianGuy 22.09.2011 / 09:05
fonte

9 respostas

30

Escreva o código mais simples que faz os testes passarem.

Nenhum de vocês fez isso, até onde eu posso ver.

Passo 1 do bebê.

Teste: Para a entrada "1,2", a soma dos números é 3

Faça o teste falhar:

throw NotImplementedException();

Faça o teste passar:

return 3;

Etapa 2 do bebê.

Teste: Para a entrada "1,2" retorno soma dos números, que é 3

Teste: Para a entrada "4,5", a soma dos números, que é 9

O segundo teste falha, então passe:

numbers = input.Split(',');
return int.Parse(numbers[0]) + int.Parse(numbers[1]);

(maneira mais simples que uma lista de se ... retornar)

Você pode certamente argumentar que a Implementação Óbvia, neste caso, mas se você estava falando sobre como fazê-lo estritamente em pequenos passos, então estes são os passos corretos, IMO.

O argumento é que, se você não escrever o segundo teste, alguma faísca brilhante poderá aparecer mais tarde e "refatorar" seu código para ler:

return input.Length; # Still satisfies the first test

E, sem executar as duas etapas, você nunca fez o segundo teste ficar vermelho (o que significa que o teste em si é suspeito).

    
por 22.09.2011 / 09:43
fonte
49

Eu acho que a segunda maneira é estupidamente tediosa. Eu vejo o valor em dar passos pequenos o suficiente, mas escrever essas minúsculas etapas de zigoto (não posso chamá-las de bebê) é apenas uma coisa e uma perda de tempo. Especialmente se o problema original que você está resolvendo já é muito pequeno por si só.

Eu sei que é treinamento e é mais sobre mostrar o princípio, mas eu acho que esses exemplos fazem TDD mais mal do que bem. Se você quiser mostrar o valor dos passos do bebê, use pelo menos um problema onde exista algum valor.

    
por 22.09.2011 / 09:17
fonte
19

Kent Beck aborda isso em seu livro Test Test Development: By Example.

O seu exemplo indica um " implementação óbvia '- você deseja retornar a soma de dois valores de entrada, e este é um algoritmo bastante básico para ser alcançado. Seu contra-exemplo cai em 'falso até você fazer isso' (embora seja um caso muito simples).

A implementação óbvia pode ser muito mais complicada do que isso - mas basicamente entra em ação quando a especificação de um método é bastante rígida - por exemplo, retornar uma versão codificada de URL de uma propriedade de classe - você não precisa perder tempo com um monte de codificações falsas.

Uma rotina de conexão com o banco de dados, por outro lado, precisaria de um pouco mais de reflexão e testes para que não houvesse uma implementação óbvia (mesmo que você já tenha escrito várias vezes em outros projetos).

Do livro:

When i use TDD in practice, I commonly shift between these two modes of implementation, When everything is going smoothly and I know what to type, I put in Obvious Implementation after Obvious Implementation (running the tests each time to ensure that what's obvious to me is still obvious to the computer). As soon as I get an unexpected red bar, I back up, shift to faking implementations, and refactor to the right code. When my confidence returns, I go back to Obvious Implementations.

    
por 22.09.2011 / 09:28
fonte
17

Eu vejo isso como seguindo a letra da lei, mas não o seu espírito.

Seus passos de bebê devem ser:

As simple as possible, but no simpler.

Além disso, o verbo no método é sum

if (input == "1,2")
   return 3;

não é uma soma, é um teste para entradas específicas.

    
por 22.09.2011 / 14:50
fonte
4

Para mim, parece bom combinar várias etapas de implementação triviais em um pouco menos trivial - eu faço isso o tempo todo também. Eu não acho que alguém precise ser religioso sobre seguir TDD toda hora ao pé da letra.

OTOH isso se aplica apenas a etapas realmente triviais como o exemplo acima. Para qualquer coisa mais complexa, que eu não possa ter em mente de uma só vez e / ou onde não tenho 110% de certeza sobre o resultado, prefiro dar um passo de cada vez.

    
por 22.09.2011 / 09:17
fonte
1

Quando se inicia o caminho de TDD, o tamanho das etapas pode ser uma questão confusa, como esta pergunta ilustra. Uma pergunta que eu sempre me fazia quando comecei a escrever aplicativos orientados a testes foi: O teste que estou escrevendo está ajudando a impulsionar o desenvolvimento de meus aplicativos? Isso pode parecer trivial e não relacionado a alguns, mas fique comigo por um momento.

Agora, quando me proponho a escrever qualquer aplicativo, geralmente começo com um teste. Quanto de um passo esse teste está em grande parte relacionado à minha compreensão do que estou tentando fazer. Se eu acho que tenho praticamente o comportamento de uma classe na minha cabeça, então o passo será grande. Se o problema que estou tentando resolver é muito menos claro, então o passo pode ser simplesmente que eu sei que está indo para um método chamado X e que ele retornará Y. Neste ponto, o método não terá nenhum parâmetro e Há uma chance de que o nome do método e o tipo de retorno sejam alterados. Em ambos os casos, os testes estão impulsionando meu desenvolvimento. Eles estão me contando coisas sobre meu aplicativo:

Esta classe que tenho em mente vai realmente funcionar?

ou

Como diabos eu vou fazer isso?

O ponto é que eu posso alternar entre grandes passos e pequenos passos em um piscar de olhos. Por exemplo, se um grande passo não funcionar e eu não conseguir enxergar de maneira óbvia, mudarei para um passo menor. Se isso não funcionar, mudarei para um passo ainda menor. Depois, há outras técnicas, como a triangulação, se eu ficar realmente preso.

Se, como eu, você é um desenvolvedor e não um testador, o ponto de usar o TDD não é escrever testes, mas escrever código. Não se preocupe em escrever um monte de pequenos testes se eles não tiverem nenhum benefício.

Espero que você tenha gostado do seu treinamento com um TDD. IMHO, se mais pessoas foram testadas infectadas, então o mundo seria um lugar melhor:)

    
por 22.09.2011 / 11:00
fonte
1

Em uma cartilha sobre testes de unidade, eu li a mesma abordagem (passos que parecem realmente minúsculos), e como uma resposta à pergunta "quão pequenos eles deveriam ser" algo que eu gostei, que foi (parafraseado) assim:

É sobre como você está confiante de que as etapas funcionam. Você pode dar grandes passos se quiser. Mas, experimente-o por algum tempo e você encontrará muita confiança equivocada em lugares que você considera como garantidos. Então, os testes ajudam você a construir uma confiança baseada em fatos.

Então, talvez o seu colega seja um pouco tímido:)

    
por 22.09.2011 / 14:18
fonte
1

Não é o ponto inteiro que a implementação do método é irrelevante, contanto que os testes tenham sucesso? A extensão dos testes falhará mais rapidamente no segundo exemplo, mas pode falhar nos dois casos.

    
por 22.09.2011 / 15:14
fonte
1

Eu estou concordando com as pessoas dizendo que nem a implementação é a mais simples.

A razão pela qual a metodologia é tão rigorosa é que ela obriga a escrever o maior número possível de testes relevantes. Retornar um valor constante para um caso de teste e chamá-lo de um passe é bom porque força você a voltar e especificar o que você realmente quer para obter algo diferente do seu programa. Usar um caso tão trivial é atirar no próprio pé em alguns aspectos, mas o princípio é que os erros se infiltram nas lacunas de sua especificação quando você tenta fazer "demais" e reduzir o requisito à implementação mais simples possível garante que um teste deve ser escrito para cada aspecto único de comportamento que você realmente quer.

    
por 22.09.2011 / 18:02
fonte

Tags