Como bebê são seus passos de bebê no TDD?

37

Hoje estávamos treinando TDD e descobrimos o seguinte ponto de incompreensão.

A tarefa é a soma de retorno de entrada "1,2" dos números que é 3. O que 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 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 implementaram o que eu fiz inicialmente.

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

Atualizar. Cometi um erro ao não esclarecer que não era o primeiro teste. Já havia alguns testes, então "retorno 3" na verdade não era o código mais simples para satisfazer o requisito.

SiberianGuy
fonte
25
Tão pequenos que os meus colegas de trabalho jorram "Ooahhh dazso cuuuuuute"
Adel
6
@Adel: Quase engasguei com meu café da manhã, teclado agora completo ou saliva e migalhas
Binary Worrier
2
@Adel, como para não-falante nativo, é bastante difícil para mim entender este humor, mas eu acho que seus colegas de trabalho como a questão :)
SiberianGuy
8
@Idsa: Está transpondo uma resposta dos colegas de trabalho quando são mostrados os primeiros passos de uma criança "Ooahhh dazso cuuuuuute" = "Oh, isso é tão fofo" (falado em uma voz cantar-canção-não-é-muito-fofa), com sua resposta ao ver os Testes de Unidade escritos por Adel, observando os passos do bebê de Testes de Unidade, eles dizem "Oh, que fofo". Reação a etapas reais - bebês = reação a testes unitários "etapas bebê".
Binary Worrier
3
@Binaryworrier desejo que eu poderia dar-lhe pontos de reais para tomar o tempo para explicar o parentess
Andrew T Finnell

Respostas:

31

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

Nenhum de vocês fez isso, tanto quanto eu posso ver.

Passo 1 do bebê.

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

Faça o teste falhar:

throw NotImplementedException();

Faça o teste passar:

return 3;

Passo 2 do bebê.

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

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

O segundo teste falha, então faça-o passar:

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

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

Certamente, você pode argumentar sobre a implementação óbvia neste caso, mas se você estava falando sobre fazê-lo estritamente nas etapas do bebê, essas são as etapas corretas, IMO.

O argumento é que, se você não escrever o segundo teste, poderá surgir alguma faísca 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).

pdr
fonte
Quanto à sua exemplo input.Length, com o mesmo sucesso que eu posso imaginar alguma aplicação incorrecta louco que não será pego de ambos os testes
SiberianGuy
@ Idsa - Sim, absolutamente, e quanto mais testes você escreve, mais louca a implementação deve ser. input.Lengthnão é tão buscado, especialmente se a entrada para o método for uma medida de algum arquivo em algum lugar e você inadvertidamente chamou seu método Size().
achou
6
+1. Com relação a como aprender TDD, este é o caminho certo. Depois de aprender, às vezes você pode ir diretamente para a implementação óbvia, mas para ter uma idéia do TDD, isso é muito melhor.
Carl Manaster 22/09
11
Eu tenho uma pergunta sobre o "teste" em si. Você escreveria um novo teste para a entrada "4,5" ou modificaria o teste original?
Mxmissile
11
@mxmissile: eu escreveria um novo teste. Não leva muito tempo e você acaba com o dobro de testes para protegê-lo quando refatorar mais tarde.
pdr
50

Eu acho que a segunda maneira é mente estupidamente estúpida. Vejo o valor de dar passos pequenos o suficiente, mas escrever esses pequenos passos de zigoto (não posso nem chamá-los de bebê) é apenas estúpido e uma perda de tempo. Especialmente se o problema original que você está resolvendo já é muito pequeno por si próprio.

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

Christophe Vanfleteren
fonte
+1 e obrigado por me fazer olhar para cima e aprender uma palavra nova (asinino)
Marjan Venema
12
+1 por chamá-lo de estupidamente estúpido. O TDD é legal e tal, mas, como em qualquer técnica moderna de programação, você deve tomar cuidado para não se perder.
stijn
2
"Especialmente se o problema original que você está resolvendo já é muito pequeno por si próprio." - Se a entrada tiver duas entradas a serem adicionadas, eu concordo com isso, mas não estou convencido quando é "dividir uma string, analise duas entradas do resultado e adicione-as". A maioria dos métodos no mundo real não é muito mais complicada do que isso. De fato, deve haver mais testes por vir, para cobrir casos extremos, como encontrar duas vírgulas, valores não inteiros etc.
pdr
4
@pdr: Concordo com você que deve haver mais testes para lidar com os casos extremos. Quando você os escreve e percebe que sua implementação precisa mudar para lidar com eles, faça isso de qualquer maneira. Eu acho que só tenho um problema em seguir as etapas do zigoto para o primeiro caminho feliz, "implementação óbvia", em vez de apenas escrever isso e seguir em frente. Não vejo o valor em escrever uma declaração if que todas as fibras do meu corpo sabem que desaparecerá no momento seguinte.
Christophe Vanfleteren
11
@ChristopheVanfleteren: Quando Beck descreve a implementação óbvia, ele usa a soma de duas entradas como exemplo e ainda lança um enorme aviso dramático sobre como você vai ficar envergonhado se o seu par / revisor puder pensar em um código mais simples que torne o teste de passagem. Essa é uma certeza absoluta se você escrever apenas um teste para este cenário. Além disso, consigo pensar em pelo menos três maneiras "óbvias" de resolver esse problema: dividir e adicionar, substituir a vírgula por + e avaliar ou usar regex. O objetivo do TDD é direcioná-lo para a escolha correta.
Pd
19

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

Seu exemplo indica uma ' implementação óbvia ' - você deseja retornar a soma dois valores de entrada, e este é um algoritmo bastante básico a ser alcançado. Seu contra-exemplo cai em 'fingir até você fazer' (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, retorna uma versão codificada em URL de uma propriedade de classe - você não precisa perder tempo com várias codificações falsificadas.

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

Do livro:

Quando uso o TDD na prática, geralmente alterno entre esses dois modos de implementação. Quando tudo está indo bem e sei o que digitar, coloco Implementação óbvia após Implementação óbvia (executando os testes a cada vez para garantir que seja óbvio para mim ainda é óbvio para o computador). Assim que recebo uma barra vermelha inesperada, faço o backup, mudo para implementações falsas e refatoro para o código certo. Quando minha confiança volta, eu volto para Implementações óbvias.

HorusKol
fonte
18

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

Seus passos de bebê devem ser:

Tão simples quanto possível, mas não mais simples.

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.

StuperUser
fonte
4

Para mim, parece bom combinar várias etapas triviais de implementação em uma ligeiramente menos trivial - eu faço isso o tempo todo também. Eu não acho que é preciso ser religioso sobre seguir o TDD todas as vezes ao pé da letra.

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

Péter Török
fonte
1

Ao iniciar o TDD pela primeira vez, o tamanho das etapas pode ser um problema confuso, como ilustra essa pergunta. As perguntas que eu costumava fazer quando comecei a escrever aplicativos orientados a teste eram: 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 fica aí comigo por um momento.

Agora, quando me propus a escrever qualquer aplicativo, normalmente começarei com um teste. Quanto de uma etapa desse teste está amplamente relacionada ao meu entendimento do que estou tentando fazer. Se eu acho que tenho praticamente o comportamento de uma classe na minha cabeça, o passo será grande. Se o problema que estou tentando resolver for muito menos claro, talvez a etapa seja simplesmente que eu saiba que está indo para um método chamado X e que retornará Y. Nesse ponto, o método nem sequer terá parâmetros e existe uma chance de que o nome do método e o tipo de retorno sejam alterados. Nos dois casos, os testes estão impulsionando meu desenvolvimento. Eles estão me dizendo coisas sobre minha inscrição:

Essa classe que eu tenho na minha cabeça 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 ver uma maneira óbvia de contornar, mudarei para um passo menor. Se isso não funcionar, mudarei para uma etapa ainda menor. Depois, existem outras técnicas, como a triangulação, se eu ficar realmente preso.

Se, como eu, você é um desenvolvedor e não um testador, o objetivo do TDD não é escrever testes, mas escrever código. Não se preocupe em escrever vários testes pequenos, se não receberem nenhum benefício.

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

lexx
fonte
1

Em uma cartilha sobre testes de unidade, li a mesma abordagem (etapas que parecem realmente muito pequenas) e como resposta à pergunta "quão pequenas devem 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, tente por algum tempo e você encontrará muita confiança equivocada em lugares que você considera como garantidos. Portanto, os testes ajudam a criar uma confiança baseada em fatos.

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

keppla
fonte
1

Não é o ponto principal que a implementação do método é irrelevante, desde que os testes tenham êxito? A extensão dos testes falhará mais rapidamente no segundo exemplo, mas poderá falhar nos dois casos.

Torsal
fonte
11
É irrelevante se você totalmente não se preocupam com desperdiçando seu tempo
SiberianGuy
1

Estou de acordo com as pessoas que dizem 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 aprovado é bom, porque força você a voltar e especificar o que realmente deseja para obter algo diferente do absurdo do seu programa. Usar um caso tão trivial é dar um tiro no pé em alguns aspectos, mas o princípio é que erros surgem nas lacunas de suas especificações quando você tenta fazer "demais" e diminui o requisito para a implementação mais simples possível, garantindo que um O teste deve ser escrito para cada aspecto exclusivo do comportamento que você realmente deseja.

Tom W
fonte
Eu adicionei uma atualização sobre "retornando um valor constante" #
SiberianGuy