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.
unit-testing
tdd
ex3v
fonte
fonte
Respostas:
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.
fonte
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
BitSet
e o modifiquei um pouco (eu também precisava rastrear os principais0
s, 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 naBitSet
interface 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 é along[]
. 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 é?
fonte
Você pode dividir o código aproximadamente em 4 categorias:
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.
fonte
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.
fonte
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.
fonte
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.
fonte