Sentido de testes de unidade sem TDD

28

Temos um novo projeto (bastante grande) que planejamos desenvolver usando o TDD.

A idéia de TDD falhou (muitas razões comerciais e não comerciais), mas agora temos uma conversa - devemos escrever testes de unidade de qualquer maneira ou não. Meu amigo diz que não há sentido (ou quase zero) em escrever testes de unidade sem TDD; devemos nos concentrar apenas nos testes de integração. Acredito o contrário, que ainda há algum sentido em escrever testes de unidade simples, apenas para tornar o código mais à prova de futuro. O que você acha?

Adicionado: Eu acho que isso não é uma duplicata >> desta questão << - eu entendo a diferença entre UT e TDD. Minha pergunta não é sobre diferenças , mas sobre o senso de escrever testes de unidade sem TDD.

ex3v
fonte
22
Estou curioso para saber o que o raciocínio o seu amigo tem para uma posição tão absurdo ...
Telastyn
11
Aposto que a grande maioria dos projetos com alguns testes de unidade não está usando TDD.
Casey
2
Quais serão seus níveis de integração? Quais serão as suas unidades? Com que frequência você estará refatorando abaixo de cada nível de teste? Qual a velocidade de execução dos seus testes de integração? Quão fácil eles serão para escrever? Quantos casos combinatórios são gerados por diferentes partes do seu código? etc ... Se você não souber as respostas, talvez seja muito cedo para tomar decisões firmes. Às vezes, o TDD é ótimo. Às vezes, os benefícios são menos claros. Às vezes, os testes de unidade são essenciais. Às vezes, um conjunto decente de testes de integração compra você quase o mesmo e é muito mais flexível ... Mantenha suas opções em aberto.
topo Reintegrar Monica
2
Como alguns conselhos práticos da experiência, não teste tudo se você não estiver fazendo TDD. Faça uma determinação sobre quais tipos de testes são valiosos. Descobri que os testes de unidade em métodos puros de entrada / saída são extraordinariamente valiosos, enquanto os testes de integração em um nível muito alto do aplicativo (por exemplo, realmente enviando solicitações da Web em um aplicativo da Web) também são extraordinariamente valiosos. Cuidado com os testes de integração de nível intermediário e testes de unidade que exigem muita configuração simulada. Assista também a este vídeo: youtube.com/watch?v=R9FOchgTtLM .
precisa saber é
Sua atualização não faz sentido no que diz respeito à pergunta que você fez. Se você entende a diferença entre TDD e testes de unidade, o que o impede de escrever testes de unidade. Votação para deixar sua pergunta encerrada, embora eu pudesse ver um argumento para fechar como "claro sobre o que você está perguntando" em vez de duplicado.

Respostas:

52

O TDD é usado principalmente (1) para garantir a cobertura, (2) e para impulsionar o design sustentável, compreensível e testável. Se você não usa TDD, não recebe cobertura de código garantida. Mas isso não significa que você deva abandonar esse objetivo e viver alegremente com 0% de cobertura.

Os testes de regressão foram inventados por um motivo. O motivo é que, a longo prazo, eles economizam mais tempo em erros evitados do que em esforços adicionais para escrever. Isso foi provado repetidamente. Portanto, a menos que você está convencido seriamente que a sua organização é muito, muito melhor em engenharia de software do que todos os gurus que recomendam testes de regressão (ou se você está pensando em ir para baixo muito em breve de modo que não é nenhuma longo prazo para você), sim, você deve ter absolutamente testes de unidade, exatamente pelo motivo que se aplica a praticamente todas as outras organizações do mundo: porque eles detectam erros mais cedo do que os testes de integração e economizam seu dinheiro. Não escrevê-las é como passar dinheiro de graça na rua.

Kilian Foth
fonte
12
"Se você não usa TDD, não recebe cobertura de código garantida.": Acho que não. Você pode desenvolver por dois dias e, nos próximos dois dias, escreve os testes. O ponto importante é que você não considera um recurso finalizado até ter a cobertura de código desejada.
Giorgio
5
@DougM - Em um mundo ideal, talvez ...
Telastyn 08/08
7
Infelizmente, o TDD anda de mãos dadas com a zombaria e até que as pessoas parem de fazer isso, tudo o que prova é que seu teste é mais rápido . TDD está morto. Teste de vida longa.
MickyD
17
O TDD não garante a cobertura do código. Essa é uma suposição perigosa. Você pode codificar contra testes, passar nesses testes, mas ainda ter casos extremos.
Robert Harvey
4
@MickyDuncan Não sei bem se entendi completamente sua preocupação. A zombaria é uma técnica perfeitamente válida usada para isolar um componente dos outros, de modo que os testes do comportamento desse componente possam ser executados independentemente. Sim, levado ao extremo, pode levar a um excesso de engenharia de software, mas qualquer técnica de desenvolvimento pode ser usada de maneira inadequada. Além disso, como o DHH afirma no artigo que você cita, a idéia de usar apenas testes completos do sistema é igualmente ruim, se não realmente pior. É importante usar o julgamento para decidir qual é a melhor maneira de testar qualquer recurso em particular .
Jules
21

Eu tenho uma anedota relevante de algo que está acontecendo agora para mim. Estou em um projeto que não usa TDD. Nosso pessoal de controle de qualidade está nos movendo nessa direção, mas somos um grupo pequeno e esse processo foi longo e prolongado.

De qualquer forma , recentemente eu estava usando uma biblioteca de terceiros para executar uma tarefa específica. Havia um problema em relação ao uso dessa biblioteca, por isso me foi pedido escrever essencialmente uma versão dessa mesma biblioteca. No total, acabaram sendo cerca de 5.000 linhas de código executável e cerca de 2 meses do meu tempo. Eu sei que linhas de código é uma métrica ruim, mas para essa resposta, sinto que é um indicador decente de magnitude.

Precisava de uma estrutura de dados específica que me permitisse acompanhar um número arbitrário de bits. Como o projeto é em Java, eu escolhi o Java BitSete o modifiquei um pouco (eu também precisava rastrear os principais 0s, o que o BitSet do Java não faz por algum motivo .....). Depois de atingir ~ 93% de cobertura, comecei a escrever alguns testes que realmente enfatizariam o sistema que eu havia escrito. Eu precisava avaliar determinados aspectos da funcionalidade para garantir que fossem rápidos o suficiente para os meus requisitos finais. Sem surpresa, uma das funções que eu havia substituído na BitSetinterface era absurdamente lenta ao lidar com grandes conjuntos de bits (centenas de milhões de bits nesse caso). Outras funções substituídas dependiam dessa única função, por isso era um gargalo enorme .

O que acabei fazendo foi ir à prancheta e descobrir uma maneira de manipular a estrutura subjacente de BitSet, que é a long[]. Projetei o algoritmo, pedi a entrada de colegas e depois comecei a escrever o código. Então, eu fiz os testes de unidade. Alguns deles quebraram e os que me apontaram exatamente para onde eu precisava procurar no meu algoritmo para corrigi-lo. Depois de corrigir todos os erros dos testes de unidade, pude dizer que a função funciona como deveria. No mínimo, eu poderia ter tanta certeza de que esse novo algoritmo funcionou como o algoritmo anterior.

Claro, isso não é à prova de balas. Se houver um erro no meu código que os testes de unidade não estejam verificando, não saberei. Mas é claro que esse mesmo bug poderia estar no meu algoritmo mais lento também. No entanto , posso dizer com um alto grau de confiança que não preciso me preocupar com a saída errada dessa função específica. Testes de unidade pré-existentes me pouparam horas, talvez dias, de tentar testar o novo algoritmo para garantir que ele estivesse correto.

Esse é o ponto de realizar testes de unidade independentemente do TDD - ou seja, os testes de unidade farão isso para você no TDD e fora do TDD da mesma forma, quando você refatorar / manter o código. Obviamente, isso deve ser associado a testes de regressão regulares, testes de fumaça, testes difusos, etc., mas o teste de unidade , como o nome indica, testa as coisas no menor nível atômico possível, o que fornece instruções sobre onde os erros apareceram.

No meu caso, sem os testes de unidade existentes, eu precisaria, de alguma forma, criar um método para garantir que o algoritmo funcionasse o tempo todo. Que, no final ... parece muito com teste de unidade , não é?

Shaz
fonte
7

Você pode dividir o código aproximadamente em 4 categorias:

  1. Simples e raramente muda.
  2. Simples e muda frequentemente.
  3. Complexo e raramente muda.
  4. Alterações complexas e frequentes.

Os testes de unidade se tornam mais valiosos (com probabilidade de detectar bugs importantes) quanto mais abaixo você estiver na lista. Nos meus projetos pessoais, quase sempre faço TDD na categoria 4. Na categoria 3, geralmente faço TDD, a menos que o teste manual seja mais simples e rápido. Por exemplo, o código antialiasing seria complexo de escrever, mas muito mais fácil de verificar visualmente do que escrever um teste de unidade; portanto, o teste de unidade só valeria a pena para mim se esse código fosse alterado com freqüência. O restante do meu código só coloco em teste de unidade depois de encontrar um bug nessa função.

Às vezes, é difícil saber de antemão em qual categoria um determinado bloco de código se encaixa. O valor do TDD é que você não perde acidentalmente nenhum dos complexos testes de unidade. O custo do TDD é todo o tempo que você gasta escrevendo os testes de unidade simples. No entanto, geralmente as pessoas com experiência em um projeto sabem com um grau razoável de certeza em que categoria diferentes partes do código se encaixam. Se você não estiver fazendo TDD, pelo menos tente escrever os testes mais valiosos.

Karl Bielefeldt
fonte
11
Ao trabalhar no código como você sugere no seu exemplo de antialiasing, acho que a melhor coisa é desenvolver o código experimentalmente e, em seguida, adicionar alguns testes de caracterização para garantir que não quebrei acidentalmente o algoritmo posteriormente. Os testes de caracterização são muito rápidos e fáceis de desenvolver, portanto, a sobrecarga de fazer isso é muito baixa.
Jules
1

Sejam testes de unidade, componente, integração ou aceitação, a parte importante é que ele deve ser automatizado. Não ter testes automatizados é um erro fatal para qualquer tipo de software, desde os CRUDs simples até os cálculos mais complexos. O raciocínio é que escrever os testes automatizados sempre custará menos do que a necessidade contínua de executar todos os testes manualmente, quando você não o fizer, por ordens de magnitude. Depois de escrever, basta pressionar um botão para ver se eles passam ou falham. Os testes manuais sempre levam muito tempo para serem executados e dependem de humanos (criaturas vivas que ficam entediadas, podem não ter atenção etc.) para poder verificar se os testes são aprovados ou reprovados. Em resumo, sempre escreva testes automatizados.

Agora, sobre o motivo pelo qual seu colega pode ser contra qualquer tipo de teste de unidade sem o TDD: Provavelmente é porque é mais difícil confiar nos testes escritos após o código de produção. E se você não pode confiar em seus testes automatizados, eles não valem nada . Após o ciclo TDD, você deve primeiro fazer uma falha no teste (pelo motivo certo) para poder escrever o código de produção para fazê-lo passar (e não mais). Esse processo está essencialmente testando seus testes, para que você possa confiar neles. Sem mencionar o fato de que escrever testes antes do código real o leva a projetar suas unidades e componentes para serem mais facilmente testáveis ​​(altos níveis de dissociação, SRP aplicado, etc ...). Embora, é claro, fazer TDD exija disciplina .

Em vez disso, se você escrever todo o código de produção primeiro, ao escrever os testes, esperará que eles sejam aprovados na primeira execução. Isso é muito problemático, porque você pode ter criado um teste que cobre 100% do seu código de produção, sem afirmar o comportamento correto (pode até não fazer nenhuma afirmação! Eu já vi isso acontecer ), pois você não pode vê-lo falhando primeiro para verificar se está falhando pelo motivo certo. Assim, você pode ter falsos positivos. Os falsos positivos acabarão quebrando a confiança em seu conjunto de testes, forçando essencialmente as pessoas a recorrer ao teste manual novamente, para que você tenha o custo de ambos os processos (testes de escrita + testes manuais).

Isso significa que você deve encontrar outra maneira de testar seus testes , como o TDD. Então, você recorre à depuração, comentando partes do código de produção, etc., para poder confiar nos testes. O problema é que o processo de "testar seus testes" é muito mais lento dessa maneira. Adicionando esse tempo ao tempo que você gastará executando testes ad-hoc manualmente (porque você não tem testes automáticos enquanto codifica o código de produção), na minha experiência, resulta em um processo geral muito mais lento do que praticar TDD "pelo livro" (Kent Beck - TDD por exemplo). Além disso, estou disposto a fazer uma aposta aqui e dizer que realmente "testar seus testes" depois que eles foram escritos requer muito mais disciplina que o TDD.

Portanto, talvez sua equipe possa reconsiderar as "razões comerciais e não comerciais" por não fazer TDD. Na minha experiência, as pessoas tendem a pensar que o TDD é mais lento em comparação com apenas escrever testes de unidade após a conclusão do código. Essa suposição é falha, como você leu acima.

MichelHenrich
fonte
0

Muitas vezes, a qualidade dos testes depende de sua procedência. Sou regularmente culpado por não fazer TDD 'real' - escrevo alguns códigos para provar que a estratégia que eu gostaria de usar realmente funciona, depois abordo cada caso em que o código deve suportar testes posteriormente. Normalmente, a unidade de código é uma classe, para lhe dar uma idéia geral de quanto trabalho farei feliz sem a cobertura do teste - ou seja, não uma quantidade grande. O que isso significa é que o significado semântico dos testes combina muito bem com o sistema em teste em seu estado 'finalizado' - porque eu os escrevi sabendo quais casos o SUT cumpre e como os cumpre.

Por outro lado, o TDD com sua política de refatoração agressiva tende a obsoletos testes pelo menos tão rápido quanto você pode escrevê-los como a interface pública do sistema sob teste muda. Pessoalmente, acho que a carga de trabalho mental de projetar as unidades funcionais do aplicativo e de manter a semântica dos testes que o cobrem em sincronia é muito alta para manter minha concentração e a manutenção do teste frequentemente diminui. A base de código termina com testes por aí que não testam nada de valor ou simplesmente estão errados. Se você tem disciplina e capacidade mental para manter o conjunto de testes atualizado, pratique o TDD da maneira mais rigorosa que desejar. Não, então achei menos bem-sucedido por esse motivo.

Tom W
fonte
0

Na verdade, o tio Bob mencionou um ponto muito interessante em um de seus vídeos do Clean Coders. Ele disse que o ciclo Vermelho-Verde-Refatorador pode ser aplicado de duas maneiras.

O primeiro é o modo TDD convencional. Escreva um teste com falha, faça o teste passar e, finalmente, refatorar.

A segunda maneira é escrever um código muito pequeno do código de produção e segui-lo imediatamente pelo teste de unidade e refatorar.

A idéia é seguir etapas muito pequenas . É claro que você perde a verificação do código de produção de que seu teste passou de vermelho para verde, mas em alguns casos em que eu trabalhava principalmente com desenvolvedores juniores que se recusavam a tentar entender o TDD, isso provou ser um pouco eficaz.

Repito novamente (e isso foi enfatizado pelo tio Bob) que a idéia é seguir etapas muito pequenas e testar imediatamente o código do código de produção que acabou de ser adicionado.

Songo
fonte
"... a idéia é seguir etapas muito pequenas e testar imediatamente a parte do código de produção que acabou de ser adicionada.": eu não concordo. O que você descreve se é bom quando você já tem uma ideia clara do que deseja fazer e deseja trabalhar nos detalhes, mas precisa ter uma visão geral primeiro. Caso contrário, seguindo etapas muito pequenas (testar, desenvolver, testar, desenvolver), você poderá se perder nos detalhes. "Se você não sabe para onde está indo, pode não chegar lá."
Giorgio