TDD vs. Produtividade

131

No meu projeto atual (um jogo, em C ++), decidi que usaria o Test Driven Development 100% durante o desenvolvimento.

Em termos de qualidade do código, isso tem sido ótimo. Meu código nunca foi tão bem projetado ou livre de erros. Não me arrependo ao visualizar o código que escrevi há um ano no início do projeto, e adquiri uma noção muito melhor de como estruturar as coisas, não apenas para ser mais facilmente testável, mas para ser mais simples de implementar e usar. .

No entanto ... já faz um ano desde que iniciei o projeto. É verdade que só posso trabalhar no meu tempo livre, mas o TDD ainda está me atrasando consideravelmente em comparação com o que estou acostumado. Eu li que a velocidade mais lenta de desenvolvimento melhora com o tempo, e eu definitivamente penso nos testes com muito mais facilidade do que costumava fazer, mas estou nisso há um ano e ainda estou trabalhando no ritmo de um caracol.

Cada vez que penso na próxima etapa que precisa de trabalho, tenho que parar toda vez e pensar em como escreveria um teste para isso, para permitir que eu escrevesse o código real. Às vezes, fico preso por horas, sabendo exatamente o código que quero escrever, mas sem saber como detalhá-lo o suficiente para cobri-lo completamente com testes. Outras vezes, pensarei rapidamente em uma dúzia de testes e passarei uma hora escrevendo testes para cobrir um pequeno pedaço de código real que, de outra forma, levaria alguns minutos para ser escrito.

Ou, depois de terminar o 50º teste para cobrir uma entidade específica no jogo e todos os aspectos de sua criação e uso, olho para a minha lista de tarefas e vejo a próxima entidade a ser codificada, e me encolho de horror ao pensar em escrever outros 50 testes semelhantes para implementá-lo.

Chegou ao ponto de que, olhando para o progresso do ano passado, estou pensando em abandonar o TDD por uma questão de "concluir o maldito projeto". No entanto, desistir da qualidade do código que acompanha ele não é algo que estou ansioso. Receio que, se eu parar de escrever testes, deixarei o hábito de tornar o código tão modular e testável.

Talvez eu esteja fazendo algo errado para ainda ser tão lento nisso? Existem alternativas que aceleram a produtividade sem perder completamente os benefícios? TAD? Menos cobertura de teste? Como outras pessoas sobrevivem ao TDD sem matar toda a produtividade e motivação?

Nairou
fonte
@ Nairou: Você sempre pode tentar "terminar o projeto"! Faça um ramo agora. Basta escrever o código lá. Mas limite o que você faz, por tempo ou número de entidades do jogo e veja se você foi mais rápido. Você pode então ignorar esse ramo, voltar ao tronco e TDD a partir daí e ver qual é a diferença.
quamrana 22/06
9
Para mim, escrever testes muito cedo é como otimizar muito cedo. Você pode estar trabalhando duro para testar o código que você removerá no futuro de qualquer maneira.
LennyProgrammers
Estou um pouco preocupado com o fato de você estar gastando horas pensando em uma maneira de criar seu código para que seja mais testável. A testabilidade é um atributo provável de um design bem fatorado, mas não deve ser o objetivo principal do design .
Jeremy
2
Quando eu estava aprendendo, tínhamos um truque para fornecer documentos de design. Primeiro escrevemos o código e depois escrevemos os documentos para descrevê-lo. Talvez você precise aprender uma quantidade moderada desse pragmatismo para o seu TDD. Se você já tem um plano em mente, talvez seja melhor inserir a maioria disso em código antes de escrever os testes. Qualquer que seja o idealismo sugerido, às vezes é melhor fazer o que você já está pronto para fazer, em vez de se distrair com outra coisa e voltar quando não estiver mais fresco.
Steve314
4
Eu vou contra a opinião popular e digo que TDD nem sempre é a escolha certa se você estiver fazendo jogos. Como alguém no gamedev.stackexchange já respondeu a essa pergunta de maneira espetacular, vou apenas vincular isso aqui .
L46kok # 28/13

Respostas:

77

Deixe-me começar agradecendo a você por compartilhar sua experiência e expressar suas preocupações ... o que devo dizer que não é incomum.

  • Tempo / produtividade: Escrever testes é mais lento do que não escrever testes. Se você escolher isso, eu concordo. No entanto, se você executar um esforço paralelo em que aplicar uma abordagem não TDD, é provável que o tempo gasto em quebrar, detectar, depurar e corrigir o código existente o coloque em negativo líquido. Para mim, o TDD é o mais rápido possível, sem comprometer minha confiança no código. Se você encontrar coisas no seu método que não agregam valor, elimine-as.
  • Número de testes: se você codificar N coisas, precisará testar N coisas. parafraseando uma das linhas de Kent Beck " Teste apenas se você quiser que funcione " .
  • Ficar preso por horas: eu também (às vezes e não> 20 minutos antes de parar a linha) .. É apenas o seu código dizendo que o design precisa de algum trabalho. Um teste é apenas mais um cliente para sua classe SUT. Se um teste estiver encontrando dificuldades para usar seu tipo, as chances são de que seus clientes de produção também.
  • Testes similares tédio: Isso precisa de mais contexto para eu escrever um contra-argumento. Dito isto, pare e pense na semelhança. Você pode conduzir esses dados de alguma forma? É possível escrever testes em um tipo base? Então você só precisa executar o mesmo conjunto de testes em cada derivação. Escute seus testes. Seja o tipo certo de preguiça e veja se consegue descobrir uma maneira de evitar o tédio.
  • Parar para pensar no que você precisa fazer em seguida (o teste / especificação) não é uma coisa ruim. Pelo contrário, é recomendável que você construa "a coisa certa". Geralmente, se não consigo pensar em como testá-lo, também não consigo pensar na implementação. É uma boa idéia esvaziar as idéias de implementação até chegar lá .. talvez uma solução mais simples seja ofuscada por um design preventivo YAGNI-ish.

E isso me leva à consulta final: como posso melhorar? Minha (ou uma) resposta é ler, refletir e praticar.

Por exemplo, ultimamente, eu fico de olho

  • se meu ritmo reflete RG [Ref] RG [Ref] RG [Ref] ou é RRRRGRRef.
  • % de tempo gasto no estado Vermelho / Erro de compilação.
  • Estou preso em um estado vermelho / quebrado?
Gishu
fonte
1
Estou muito curioso sobre o seu comentário sobre a condução dos dados nos testes. Você quer dizer apenas um único conjunto de testes que processam dados externos (como de um arquivo) em vez de testar novamente um código semelhante? No meu jogo, tenho várias entidades, e cada uma é bastante diferente, mas há algumas coisas comuns que são feitas com elas (serializando-as pela rede, certificando-se de que não sejam enviadas a jogadores inexistentes etc.) Até agora, não encontrei uma maneira de consolidar isso, e também temos conjuntos de testes para cada um que são quase idênticos, diferentes apenas em qual entidade eles usam e em quais dados eles contêm.
@ Nairoi - Não sei ao certo qual corredor de teste você está usando. Acabei de aprender um nome para o que queria transmitir. Padrão de fixação abstrato [ goo.gl/dWp3k] . Isso ainda requer que você escreva o número de luminárias que houver tipos concretos de SUT. Se você quiser ser ainda mais conciso, consulte os documentos do seu corredor. por exemplo, o NUnit suporta dispositivos de teste parametrizados e genéricos (agora que eu procurei) goo.gl/c1eEQ Parece exatamente o que você precisa.
Gishu
Interessante, nunca ouvi falar de acessórios abstratos. Atualmente, uso o UnitTest ++, que possui acessórios, mas não os abstratos. Os equipamentos são muito literais, apenas uma maneira de consolidar o código de teste que você repetiria em cada teste, para um determinado grupo de testes.
@asgeo - não pode editar esse comentário .. o link pegou um arrasto bracket.This devem trabalhar - goo.gl/dWp3k
Gishu
+1 para 'ficar preso é um sintoma do design precisa de mais trabalho', embora .. o que acontece quando você fica preso (como eu) no design?
Lurscher
32

Você não precisa de 100% de cobertura de teste. Seja pragmático.

Alistair
fonte
2
Se você não possui 100% de cobertura de teste, não tem 100% de confiança.
Christopher Mahan
60
Você não tem 100% de confiança, mesmo com 100% de cobertura de teste. Esse é o teste 101. Os testes não podem demonstrar que o código está livre de defeitos; pelo contrário, eles só podem demonstrar que contém defeitos.
CesarGon
7
Pelo que vale, um dos advogados mais apaixonados do TDD, Bob Martin, não recomenda 100% de cobertura - blog.objectmentor.com/articles/2009/01/31/… . Na indústria de manufatura (concedida, diferente do software em muitos aspectos), ninguém busca 100% de confiança porque pode gastar uma fração do esforço para ter 99% de confiança.
Chance
Também (pelo menos na última vez que verifiquei as ferramentas que possuímos), os relatórios de cobertura de código referem-se à execução de linhas, mas não incluem cobertura de valor. como havia uma linha como a = x + ye, embora todas as linhas no código tenham sido executadas em testes, os testes foram testados apenas para o caso em que y = 0, portanto o bug (deveria ter sido a = x - y) nunca foi encontrado nos testes.
Pete Kirkham 26/02
@ Chance - eu li o livro de Robert Martin "Clean coder ..." algum nome longo. Dizia naquele livro que deveria ser assintoticamente 100% coberto pelos testes, o que é próximo a 100%. E o link do blog não funciona mais.
precisa saber é o seguinte
22

TDD ainda está me atrasando consideravelmente

Isso é realmente falso.

Sem o TDD, você passa algumas semanas escrevendo código que funciona principalmente e passa o próximo ano "testando" e corrigindo muitos (mas não todos) dos bugs.

Com o TDD, você passa um ano escrevendo código que realmente funciona. Depois, você faz o teste final de integração por algumas semanas.

O tempo decorrido provavelmente será o mesmo. O software TDD terá uma qualidade substancialmente melhor.

S.Lott
fonte
6
Então, por que preciso do TDD? "O tempo decorrido é o mesmo"
21
@ Peter Long: qualidade do código. O ano "testando" é como terminamos com um software ruim que geralmente funciona.
S.Lott
1
@ Peter, você deve estar brincando. A qualidade da solução TDD será muito superior.
Mark Thomas
7
Por que preciso do TDD? Kent Beck lista a paz de espírito como um grande problema, e é muito atraente para mim. Estou com medo constante de quebrar coisas quando trabalho no código sem testes de unidade.
7
@ Peter Long: "O tempo decorrido é o mesmo" ... e, a qualquer momento, você pode fornecer código de trabalho .
Frank Shearar
20

Ou, depois de terminar o 50º teste para cobrir uma entidade específica no jogo e todos os aspectos de sua criação e uso, olho para a minha lista de tarefas e vejo a próxima entidade a ser codificada, e me encolho de horror ao pensar em escrever outros 50 testes semelhantes para implementá-lo.

Isso me faz pensar em quanto você está seguindo a etapa "Refatorar" do TDD.

Quando todos os seus testes estiverem passando, é hora de refatorar o código e remover a duplicação. Embora as pessoas geralmente se lembrem disso, às vezes esquecem que também é hora de refatorar seus testes , remover a duplicação e simplificar as coisas.

Se você tiver duas entidades que se fundem em uma para permitir a reutilização de código, considere mesclar seus testes também. Você realmente só precisa testar diferenças incrementais no seu código. Se você não realiza manutenção regularmente em seus testes, eles podem se tornar rapidamente difíceis de manejar.

Alguns pontos filosóficos sobre TDD que podem ser úteis:

  • Quando você não consegue descobrir como escrever um teste, apesar da extensa experiência em escrever testes, esse é definitivamente um cheiro de código . De alguma forma, seu código não possui modularidade, o que dificulta a escrita de testes pequenos e simples.
  • Descobrir um pouco de código é perfeitamente aceitável ao usar TDD. Escreva o código desejado, para ter uma idéia de como ele é, exclua o código e comece com testes.
  • Vejo praticar TDD extremamente rigoroso como uma forma de exercício. Quando você começar, escreva definitivamente um teste todas as vezes e escreva o código mais simples para fazer o teste antes de prosseguir. No entanto, isso não é necessário quando você se sentir mais confortável com a prática. Não tenho um teste de unidade para cada caminho de código possível que escrevo, mas, com a experiência, sou capaz de escolher o que precisa ser testado com um teste de unidade e o que pode ser coberto pelo teste de integração ou funcional. Se você pratica o TDD de maneira estrita há um ano, imagino que você também esteja próximo desse ponto.

EDIT: No tópico da filosofia do teste de unidade, acho que pode ser interessante para você ler: O Caminho do Testivus

E um ponto mais prático, se não necessariamente muito útil,:

  • Você menciona o C ++ como sua linguagem de desenvolvimento. Eu pratiquei TDD extensivamente em Java, usando excelentes bibliotecas como JUnit e Mockito. No entanto, eu achei o TDD em C ++ muito frustrante devido à falta de bibliotecas (em particular, estruturas de simulação) disponíveis. Embora esse ponto não o ajude muito na sua situação atual, espero que você o leve em consideração antes de abandonar o TDD completamente.
jaustin
fonte
4
Refatorar testes é perigoso. Parece que ninguém fala sobre isso, mas é. Certamente não tenho testes de unidade para testar meus testes de unidade. Ao refatorar para reduzir a duplicação, você geralmente aumenta a complexidade (porque seu código se torna mais geral). Isso significa que é mais provável que haja um erro em seus testes .
Scott Whitlock
2
Não concordo que a refatoração de testes seja perigosa. Você só está refatorando quando tudo está passando; portanto, se você fizer uma refatoração e tudo ainda estiver verde, estará bem. Se você pensa que precisa escrever testes para os testes, sinto que isso é um indicador de que você precisa escrever testes mais simples.
jaustin
1
O C ++ é difícil de testar na unidade (a linguagem não faz coisas facilmente que facilitam a zombaria). Notei que funções que são "funções" (operam apenas com argumentos, os resultados são apresentados como valores de retorno / parâmetros) são muito mais fáceis de testar do que "procedimentos" (retorno nulo, sem argumentos). Descobri que pode ser mais fácil testar o código C modular bem elaborado do que o código C ++. Você não precisa escrever em C, mas pode seguir o exemplo modular de C. Parece completamente insano, mas coloquei testes de unidade em "C ruim", onde tudo era global e era super fácil - todo o estado está sempre disponível !.
anon
2
Eu acho que isso é verdade. Eu faço muito RedGreenRedGreenRedGreen (ou, mais frequentemente, RedRedRedGreenGreenGreen), mas raramente refato. Meus testes certamente nunca foram refatorados, pois eu sempre senti que perderia ainda mais tempo sem codificação. Mas posso ver como pode ser a causa dos problemas que estou enfrentando agora. Hora de eu pensar seriamente em refatoração e consolidação.
Nairou
1
Estrutura de simulação do Google C ++ (integrada ao teste de google C ++ fw) - biblioteca de simulação muito poderosa - flexível, rica em recursos - bastante comparável a qualquer outra estrutura de simulação existente.
ratkok
9

Pergunta muito interessante.

O que é importante notar é que o C ++ não é facilmente testável e os jogos, em geral, também são um candidato muito ruim para o TDD. Você não pode testar se o OpenGL / DirectX desenha triângulo vermelho com o driver X e amarelo com o driver Y facilmente. Se o vetor normal do mapa de resposta não for invertido após a transformação do shader. Nem você pode testar problemas de recorte em versões de driver com diferentes precisões e assim por diante. O comportamento indefinido do desenho devido a chamadas incorretas também pode ser testado apenas com a revisão precisa do código e o SDK em mãos. O som também é um candidato ruim. Multithreading, que novamente é bastante importante para jogos, é praticamente inútil para testes de unidade. Então é difícil.

Basicamente, os jogos são muito GUI, som e threads. A GUI, mesmo com componentes padrão para os quais você pode enviar WM_, é difícil de testar na unidade.

Portanto, o que você pode testar são classes de carregamento de modelos, classes de carregamento de texturas, bibliotecas de matrizes e algo assim, que não é muito código e, muitas vezes, não é muito reutilizável, se é apenas o seu primeiro projeto. Além disso, eles são compactados em formatos proprietários, portanto, não é bem provável que a entrada de terceiros possa diferir muito, a menos que você libere ferramentas de modificação, etc.

Por outro lado, eu não sou um guru ou evangelista da TDD, então leve tudo isso com um grão de sal.

Eu provavelmente escreveria alguns testes para os principais componentes principais (por exemplo, biblioteca de matrizes, biblioteca de imagens). Adicione abort()várias entradas inesperadas em todas as funções. E o mais importante, concentre-se em código resistente / resiliente que não se quebre facilmente.

Com relação a um erro, o uso inteligente de C ++, RAII e um bom design ajudam bastante a evitá-lo.

Basicamente, você tem muito o que fazer apenas para cobrir o básico, se quiser lançar o jogo. Não tenho certeza se o TDD vai ajudar.

Codificador
fonte
3
+1 Eu realmente gosto do conceito de TDD e o uso sempre que posso, mas você levanta um ponto muito importante no qual os defensores do TDD estão curiosamente silenciosos. Como você indicou, existem muitos tipos de programação para os quais escrever testes de unidade significativos é extremamente difícil, se não impossível. Use TDD onde isso fizer sentido, mas alguns tipos de código são melhor desenvolvidos e testados de outras maneiras.
Mark Heath
@ Mark: sim, ninguém parece se importar com os testes de integração hoje em dia, pensando que, por terem um conjunto de testes automatizado, tudo funcionará magicamente quando reunidos e testados com dados reais.
Gbjbaanb
Concordo com isso. Obrigado por uma resposta pragmática que não prescreve dogmaticamente o TDD como a resposta para tudo, em vez do que é, que é apenas mais uma ferramenta no kit de ferramentas do desenvolvedor.
jb
6

Concordo com as outras respostas, mas também quero acrescentar um ponto muito importante: custos de refatoração !!

Com testes de unidade bem escritos, você pode reescrever com segurança seu código. Primeiro, testes de unidade bem escritos fornecem excelente documentação sobre a intenção do seu código. Segundo, quaisquer efeitos colaterais infelizes da sua refatoração serão detectados no conjunto de testes existente. Assim, você garantiu que as suposições do seu código antigo também são verdadeiras para o seu novo código.

Morten
fonte
4

Como outras pessoas sobrevivem ao TDD sem matar toda a produtividade e motivação?

Isso é completamente diferente das minhas experiências. Você é incrivelmente inteligente e escreve código sem bugs (por exemplo, desligado por um erro) ou não percebe que seu código possui bugs que impedem o funcionamento do programa e, portanto, não estão realmente concluídos.

TDD é sobre ter a humildade de saber que você (e eu!) Cometemos erros.

Para mim, o tempo de criação de unittests é mais do que economizado no tempo de depuração reduzido para projetos que são feitos usando TDD desde o início.

Se você não cometer erros, talvez o TDD não seja tão importante para você quanto para mim!

Tom
fonte
Bem, você tem erros em seu código TDD bem;)
Coder
Isso é verdade! mas eles tendem a ser um tipo diferente de erro, se o TDD for feito corretamente. Eu acho que dizer que o código precisa estar 100% livre de bugs para concluir não está certo. Embora se define um bug como um desvio do comportamento de teste de unidade definido, então eu acho que é :) livre bug
Tom
3

Eu tenho apenas algumas observações:

  1. Parece que você está tentando testar tudo . Você provavelmente não deveria, apenas os casos de alto risco e de borda de uma parte específica de código / método. Tenho certeza de que a regra 80/20 se aplica aqui: você gasta 80% escrevendo testes nos últimos 20% do seu código ou casos que não são cobertos.

  2. Prioritizar. Entre no desenvolvimento ágil de software e faça uma lista do que você realmente precisa fazer para lançar em um mês. Então solte, assim mesmo. Isso fará você pensar sobre a prioridade dos recursos. Sim, seria legal se seu personagem pudesse dar um retorno, mas isso tem valor comercial ?

O TDD é bom, mas apenas se você não deseja obter 100% de cobertura de teste, e não o impede de produzir valor comercial real (ou seja, recursos, coisas que adicionam algo ao seu jogo).

Cthulhu
fonte
1

Sim, escrever testes e código pode levar mais tempo do que escrever código - mas escrever código e testes de unidade associados (usando TDD) é muito mais previsível do que escrever código e depois depurá-lo.

A depuração é quase eliminada ao usar o TDD - o que torna todo o processo de desenvolvimento muito mais previsível e, no final - sem dúvida mais rápido.

Refatoração constante - é impossível fazer uma refatoração séria sem um conjunto abrangente de testes de unidade. A maneira mais eficiente de construir essa rede de segurança baseada em testes de unidade é durante o TDD. O código bem refatorado melhora significativamente a produtividade geral do designer / equipe que mantém o código.

Ratkok
fonte
0

Considere restringir o escopo do seu jogo e levá-lo aonde alguém possa jogá-lo ou você o libera. Manter seus padrões de teste sem ter que esperar muito tempo para lançar seu jogo pode ser um meio termo para mantê-lo motivado. Os comentários de seus usuários podem fornecer benefícios a longo prazo e seus testes permitem que você se sinta confortável com as adições e alterações.

JeffO
fonte