Por que não escrever todos os testes de uma vez ao fazer TDD?

55

O ciclo Vermelho - Verde - Refatorador para TDD está bem estabelecido e aceito. Escrevemos um teste de unidade com falha e fazemos com que seja aprovado da maneira mais simples possível. Quais são os benefícios dessa abordagem ao escrever muitos testes de unidade com falha para uma classe e fazer com que todos passem de uma só vez.

A suíte de testes ainda protege você contra a gravação de código incorreto ou cometer erros no estágio de refatoração, então qual é o problema? Às vezes, é mais fácil escrever todos os testes de uma classe (ou módulo) primeiro como uma forma de "despejo cerebral" para anotar rapidamente todo o comportamento esperado de uma só vez.

RichK
fonte
20
Faça o que funciona melhor para você (após algumas experiências). Seguir cegamente o dogma nunca é uma coisa boa.
22812 Michael Borgwardt
6
Ouso dizer que escrever todos os seus testes de uma só vez é como escrever todo o código do seu aplicativo de uma só vez.
Michael Haren
11
@MichaelHaren todos os ensaios para uma classe (ou módulo funcional), a pena de confusão
RichK
3
Resolvendo o problema de "despejo cerebral": Às vezes, há pontos no teste / codificação quando você percebe a necessidade de vários testes de entrada específicos diferentes e há uma tendência a querer capitalizar a clareza dessa realização antes de se distrair com o minúcias da codificação. Normalmente, eu mantenho isso mantendo uma lista separada (por exemplo, Mylyn), ou então com uma lista de comentários na classe Test de diferentes coisas que quero lembrar de testar (por exemplo, // testar caso nulo). No entanto, ainda codifico apenas um teste de cada vez e, em vez disso, percorro a lista sistematicamente.
Sam Goldberg
11
bem, eu não sei por que ninguém mencionou isso, mas você NÃO PODE escrever todos os testes de uma só vez. Escrever todos os testes antes da mão é exatamente o mesmo que fazer o BDUF . E o que a história nos ensinou sobre o BDUF? Ele quase nunca funciona.
Songo

Respostas:

50

O design orientado a teste é sobre como acertar sua API , não o código.

O benefício de escrever os testes com falha mais simples primeiro é que você obtém sua API (que é basicamente o que você está projetando em tempo real) da maneira mais simples possível. Na frente.

Quaisquer usos futuros (que são os próximos testes que você escreve) passarão do design simples inicial, em vez de um design subótimo para lidar com casos mais complexos.

user1249
fonte
Excelentes pontos! Às vezes, estamos tão imersos no teste do código que às vezes ignoramos a importância da API e do modelo de domínio antes mesmo de escrever seu primeiro teste.
Maple_shaft
+1 por realmente abordar a intenção do Desenvolvimento Orientado a Testes .
Joshua Drake
76

Quando você escreve um teste, você se concentra em uma coisa.
Com muitos testes, você concentra sua atenção em muitas tarefas, portanto não é uma boa ideia.

Abyx
fonte
8
Quem negaria isso ?!
CaffGeek #
6
@ Chame eu não era o voto negativo, mas acredito que esta resposta erra o óbvio. O desenvolvimento orientado a testes é sobre o uso de testes para orientar o design do código. Você escreve o teste individualmente para evoluir o design, não apenas para testabilidade. Se fosse apenas sobre os artefatos de teste, seria uma boa resposta, mas, como está, faltam algumas informações cruciais.
Joshua Drake
7
Eu não diminuí o voto disso, mas; Eu pensei sobre isso. É uma resposta muito breve para uma pergunta complexa.
Mark Weston
2
+1 por se concentrar em uma coisa de cada vez, nossa capacidade de realizar várias tarefas ao mesmo tempo é superestimada.
Cctan
É a resposta mais simples que pode funcionar.
DNA
27

Uma das dificuldades ao escrever testes de unidade é que você está escrevendo um código e que por si só pode ser propenso a erros. Também há a possibilidade de você precisar alterar seus testes posteriormente, como resultado de um esforço de refatoração ao escrever seu código de implementação. Com o TDD, isso significa que você pode acabar se empolgando demais com seus testes e precisa reescrever muitos códigos de teste essencialmente "não testados" à medida que sua implementação amadurece ao longo do projeto. Uma maneira de evitar esse tipo de problema é simplesmente se concentrar em fazer uma única coisa de cada vez. Isso garante que você minimize o impacto de quaisquer alterações em seus testes.

Obviamente, isso se resume principalmente à maneira como você escreve seu código de teste. Você está escrevendo um teste de unidade para cada método individual ou está escrevendo testes focados em recursos / requisitos / comportamentos? Outra abordagem poderia ser usar uma abordagem orientada a comportamentos, com uma estrutura adequada, e concentrar-se em escrever testes como se fossem especificações. Isso significaria adotar o método BDD ou adaptar o teste BDD, se você desejar manter o TDD mais formalmente. Como alternativa, você pode se ater inteiramente ao paradigma TDD, mas alterar a maneira como escreve testes para que, em vez de se concentrar inteiramente nos métodos de teste individualmente, teste comportamentos mais geralmente como um meio de satisfazer as especificidades dos recursos de requisitos que você está implementando.

Independentemente da abordagem específica adotada, em todos os casos que descrevi acima, você usa uma abordagem de teste primeiro; portanto, embora possa ser tentador simplesmente baixar seu cérebro em um adorável conjunto de testes, você também deseja combater o tentação de fazer mais do que é absolutamente necessário. Sempre que estou prestes a iniciar um novo conjunto de testes, começo a repetir o YAGNI para mim mesmo e às vezes até o coloco em um comentário no meu código para me lembrar de manter o foco no que é imediatamente importante e de fazer apenas o mínimo necessário para satisfazer as requisitos do recurso que estou prestes a implementar. Aderir ao Red-Green-Refactor ajuda a garantir que você faça isso.

S.Robins
fonte
Estou feliz que você apontou a distinção entre como alguém escreve seu código de teste. Alguns gostam de escrever um único teste de unidade mestre que cubra todas as possibilidades realistas de entradas para uma única função ou método. Outros adotam uma abordagem mais BDD com seus testes de unidade. Essa distinção é importante quando determinar se escrever um conjunto inteiro de testes é importante é necessariamente uma má prática ou não. Great insight!
Maple_shaft
17

Eu acho que, ao fazer isso, você perde o processo de TDD. Ao escrever todos os seus testes no início, você não está realmente passando pelo processo de desenvolvimento usando o TDD. Você está simplesmente adivinhando de antemão quais testes precisará. Esse será um conjunto de testes muito diferente daqueles que você acaba escrevendo, se os fizer um de cada vez, à medida que desenvolve seu código. (A menos que seu programa seja trivial por natureza.)

ZweiBlumen
fonte
11
A maioria dos aplicativos corporativos e de negócios é tecnicamente trivial por natureza e, como a maioria dos aplicativos é comercial e empresarial, a maioria dos aplicativos também é trivial por natureza.
Maple_shaft
5
@ maple_shaft - a tecnologia pode ser trivial, mas as regras de negócios não são. Tente criar um aplicativo para 5 gerentes, todos com requisitos diferentes e se recusam a ouvir alguns BS sobre seu design minimalista, simples e elegante.
Jeffo
5
@ Jeffff 1) Não é BS. 2) Um design minimalista elegante requer boas habilidades de desenvolvimento de software. 3) A capacidade de atenuar os requisitos de 5 gerentes diferentes que não têm mais de 5 minutos por semana para gastar com você e ainda assim criar um design minimalista requer um excelente desenvolvedor de software. Dica profissional: o desenvolvimento de software é mais do que apenas habilidades de codificação, é negociação, conversação e apropriação. Você tem que ser um cachorro Alpha e morder de volta às vezes.
Maple_shaft
11
Se bem entendi, esta resposta está implorando a pergunta.
amigos estão dizendo sobre Konrad Rudolph
11
@ maple_shaft Acho que era isso que Jeff O estava fazendo com seu comentário, não?
ZweiBlumen
10

Eu “escrevo” todos os testes que consigo pensar de frente enquanto assalto o cérebro, mas escrevo cada teste como um único comentário que descreve o teste.

Em seguida, converto um teste em código e faço o trabalho para que ele seja compilado e aprovado . Freqüentemente, decido que não preciso de todos os testes que pensei ter feito, ou preciso de testes diferentes; essas informações são provenientes da escrita do código para que os testes sejam aprovados.

O problema é que você não pode escrever um teste no código até ter criado o método e as classes testadas, caso contrário, você obterá muitos erros do compilador que lhe permitirão trabalhar em um único teste de cada vez.

Agora, se você estiver usando um sistema como fluxo de especificações quando os testes forem escritos em “inglês”, convém que os clientes concordem com um conjunto de testes enquanto você tiver o tempo deles, em vez de criar apenas um único teste.

Ian
fonte
11
Sim, ao concordar com as respostas acima que apontam os problemas de codificação de todos os seus testes primeiro, acho muito útil despejar minha compreensão geral de como o método atual deve se comportar como um conjunto de descrições de testes sem nenhum código. O processo de anotá-las tende a esclarecer se eu entendo completamente o que é necessário para o código que estou prestes a escrever e se há casos extremos nos quais não pensei. Sinto-me muito mais confortável codificando o primeiro teste e depois passando-o depois de descrever minha "visão geral" de como o método deve funcionar.
Mark Weston
10

A idéia por trás do TDD são iterações rápidas.

Se você tiver grandes faixas de testes que precisam ser gravadas antes de escrever seu código, é difícil refatorar iterativamente o código.

Sem a refatoração fácil de código, você perde muitos dos benefícios do TDD.

linkerro
fonte
5

Na minha experiência (limitada) com TDD, posso dizer-lhe que toda vez que eu quebro a disciplina de escrever um teste de cada vez, as coisas correm mal. É uma armadilha fácil de cair. "Oh, esse método é trivial", você pensa consigo mesmo, "então eu vou nocautear esses dois outros testes relacionados e seguir em frente". Bem, adivinhe? Nada é tão trivial quanto parece. Toda vez que caí nessa armadilha, acabava depurando algo que achava fácil, mas havia casos estranhos nos cantos. E desde que eu escrevi vários testes ao mesmo tempo, foi muito trabalhoso rastrear onde estava o erro.

Se você precisar de um despejo de informações no cérebro, terá várias opções:

  • Quadro branco
  • Histórias de usuários
  • Comentários
  • Boa caneta e papel velho

Observe que em nenhum lugar nesta lista está o compilador. :-)

Kristo
fonte
5

Você está assumindo que sabe como será o seu código antes de escrevê-lo. TDD / BDD é tanto um processo de design / descoberta quanto um processo de controle de qualidade. Para um determinado recurso, você escreve o teste mais simples que verifica se o recurso é satisfeito (às vezes isso pode exigir vários devido à complexidade de um recurso). O primeiro teste que você escreve é ​​carregado com suposições de como será o código de trabalho. Se você escrever todo o conjunto de testes antes de escrever a primeira linha de código para apoiá-lo, estará fazendo uma série de suposições não verificadas. Em vez disso, escreva uma suposição e verifique-a. Então escreva o próximo. No processo de verificação da próxima suposição, você pode apenas quebrar uma suposição anterior para voltar e alterar essa primeira suposição para corresponder à realidade ou alterar a realidade para que a primeira suposição ainda se aplique.

Pense em cada teste de unidade que você escreve como uma teoria em um caderno científico. Ao preencher o caderno, você prova suas teorias e forma novas. Às vezes, provar uma nova teoria desmente uma teoria anterior, então você deve corrigi-la. É mais fácil provar uma teoria de cada vez do que tentar provar digamos 20 de uma vez.

Michael Brown
fonte
O TDD supõe que você saiba como será o seu código antes de escrevê-lo também, apenas em partes menores.
Michael Shaw
4

O TDD é uma abordagem altamente iterativa, que (na minha experiência) se encaixa melhor nas formas de desenvolvimento do mundo real. Normalmente, minha implementação toma forma gradualmente durante esse processo, e cada etapa pode trazer mais perguntas, idéias e idéias para teste. Isso é ideal para manter minha mente focada na tarefa real e é muito eficiente, porque eu só preciso manter um número limitado de coisas na memória de curto prazo a qualquer momento. Isso, por sua vez, reduz a possibilidade de erros.

Sua idéia é basicamente uma abordagem Big Test Up Front, com a qual o IMHO é mais difícil de lidar e pode se tornar mais inútil. E se você perceber, no meio do trabalho, que sua abordagem não é boa, sua API é falha e você precisa começar tudo de novo ou usar uma biblioteca de terceiros? Então, grande parte do trabalho realizado para escrever seus testes antecipadamente se torna um esforço desperdiçado.

Dito isto, se isso funcionar para você, tudo bem. Eu posso imaginar que, se você trabalha com uma especificação técnica fixa e detalhada, em um domínio com experiência íntima e / ou em uma tarefa relativamente pequena, você pode ter a maioria ou todos os casos de teste necessários prontos e sua implementação clara desde o começo. Então, pode fazer sentido começar escrevendo todos os testes de uma só vez. Se sua experiência é que isso o torna mais produtivo a longo prazo, você não precisa se preocupar muito com os livros de regras :-)

Péter Török
fonte
4

Além de apenas pensar em uma coisa, um paradigma do TDD é escrever o mínimo de código possível para passar no teste. Quando você escreve um teste de cada vez, é muito mais fácil ver o caminho para escrever código apenas o suficiente para que o teste seja aprovado. Com todo um conjunto de testes a serem aprovados, você não encontra o código em pequenas etapas, mas precisa dar um grande salto para que todos passem de uma só vez.

Agora, se você não se limitar a escrever o código para fazer com que todos passem "de uma só vez", mas escreva apenas o código suficiente para passar um teste de cada vez, ainda pode funcionar. Você precisaria ter mais disciplina para não apenas prosseguir e escrever mais código do que o necessário. Depois de iniciar esse caminho, você se abre para escrever mais código do que os testes descrevem, o que pode ser não testado , pelo menos no sentido de que não é conduzido por um teste e talvez no sentido de que não é necessário (ou exercido) por qualquer teste.

Descobrir o que o método deve fazer, como comentários, histórias, uma especificação funcional, etc., é perfeitamente aceitável. Eu esperaria para traduzi-los em testes, um de cada vez.

A outra coisa que você pode perder escrevendo os testes de uma só vez é o processo de raciocínio pelo qual a aprovação em um teste pode fazer com que você pense em outros casos de teste. Sem um banco de testes existentes, você precisa pensar no próximo caso de teste no contexto do último teste aprovado. Como eu disse, ter uma boa idéia do que o método deve fazer é muito bom, mas muitas vezes me vi encontrando novas possibilidades que não havia considerado a priori, mas que só ocorreram no processo de escrever o testes. Existe o perigo de você sentir falta deles, a menos que tenha o hábito específico de pensar em quais novos testes posso escrever que ainda não possuo.

tvanfosson
fonte
3

Eu trabalhei em um projeto em que os desenvolvedores que escreveram os testes (com falha) eram diferentes dos desenvolvedores que implementavam o código necessário para fazê-los passar e eu achei realmente eficaz.

Nesse caso, apenas os testes relacionados à iteração atual foram gravados uma vez. Então, o que você sugere é perfeitamente possível nesse tipo de cenário.

user2567
fonte
2
  • Então você tenta se concentrar em muitas coisas ao mesmo tempo.
  • Durante a implementação para que todos os testes sejam aprovados, você não possui uma versão funcional do seu aplicativo. Se você precisar implementar muito, não terá uma versão funcional por um longo tempo.
magomi
fonte
2

O ciclo Red-Green-Refactor é uma lista de verificação destinada a desenvolvedores novos no TDD. Eu diria que é uma boa idéia seguir esta lista de verificação até que você saiba quando segui-la e quando você pode quebrá-la (ou seja, até que você saiba que não precisa fazer essa pergunta no stackoverflow :)

Tendo feito o TDD por quase uma década, posso dizer que raramente, se é que alguma vez, escrevo muitos testes com falha antes de escrever o código de produção.

Torbjörn Kalin
fonte
1

Você está descrevendo o BDD, onde algumas partes interessadas externas têm uma especificação executável. Isso pode ser benéfico se houver uma especificação inicial predeterminada (por exemplo, uma especificação de formato, padrão industrial ou onde o programador não seja o especialista em domínio).

A abordagem normal é cobrir gradualmente mais e mais testes de aceitação, que é o progresso visível para o gerente de projeto e o cliente.

Você geralmente tem esses testes especificados e executados em uma estrutura BDD como Cucumber, Fitnesse ou algo parecido.

No entanto, isso não é algo que você confunde com seus testes de unidade, que estão muito mais próximos dos detalhes da implementação, com muitos casos de borda relacionados à API, problemas de inicialização, etc., fortemente focados no item em teste , que é um artefato de implementação .

A disciplina de refatorar verde-vermelho tem muitos benefícios, e a única vantagem que você pode esperar, digitando-os na frente, é empatar.

Tormod
fonte
1

Um teste de cada vez: a principal vantagem é o foco em uma coisa. Pense no design mais profundo: você pode se aprofundar e manter o foco com um loop de feedback rápido. Você pode perder o escopo de todo o problema! É nesse momento que a refatoração (grande) entra em cena. Sem ele, o TDD não funciona.

Todos os testes: a análise e o design podem revelar mais sobre o escopo do problema. Pense no design amplo. Você analisa o problema de mais ângulos e adiciona informações da experiência. É inerentemente mais difícil, mas pode gerar benefícios interessantes - menos refatoração - se você fizer 'apenas o suficiente'. Cuidado, é fácil analisar demais e, no entanto, errar completamente o alvo!

Acho difícil recomendar geralmente preferir um ou outro, porque os fatores são muitos: experiência (especialmente com o mesmo problema), conhecimento e habilidades de domínio, compatibilidade do código para refatoração, complexidade do problema ...

Eu acho que se focarmos mais estreitamente em aplicativos de negócios típicos, o TDD com sua abordagem rápida de quase tentativa e erro geralmente venceria em termos de eficácia.

MaR
fonte
1

Supondo que sua estrutura de teste a suporte, o que eu sugeriria é que, em vez de implementar os testes que você deseja braindump, escreva testes pendentes descritivos que você implementará posteriormente. Por exemplo, se sua API deve fazer foo e bar, mas não biz, basta adicionar o código a seguir (este exemplo está em rspec) para seu conjunto de testes e atacá-los um por um. Você pensa rapidamente e pode resolver todos os seus problemas um por um. Quando todos os testes forem aprovados, você saberá quando resolveu todos os seus problemas durante o braindump.

describe "Your API" do

  it "should foo" do
    pending "braindump from 4/2"
  end

  it "should bar" do
    pending "braindump from 4/2"
  end

  it "should not biz" do
    pending "braindump from 4/2"
  end

end
Ransom Briggs
fonte