Por um tempo eu tenho tentado aprender a escrever testes de unidade para o meu código.
Inicialmente, comecei a fazer TDD verdadeiro, onde não escreveria nenhum código até escrever um teste com falha primeiro.
No entanto, recentemente tive um problema espinhoso para resolver, que envolvia muito código. Depois de passar algumas semanas escrevendo testes e depois codificando, cheguei à infeliz conclusão de que toda a minha abordagem não funcionaria e teria que jogar duas semanas de trabalho e começar de novo.
Essa é uma decisão bastante ruim quando você acabou de escrever o código, mas quando você também escreveu centenas de testes de unidade, fica ainda mais emocionalmente difícil simplesmente jogar tudo fora.
Não consigo deixar de pensar que perdi 3 ou 4 dias de esforço escrevendo esses testes, quando eu poderia apenas juntar o código para a prova de conceito e depois escrever os testes depois que fiquei satisfeito com a minha abordagem.
Como as pessoas que praticam TDD lidam adequadamente com essas situações? Em alguns casos, existe um argumento para adulterar as regras ou você sempre escreve os testes de maneira servil primeiro, mesmo quando esse código pode ser inútil?
fonte
Respostas:
Sinto que há duas questões aqui. A primeira é que você não percebeu antecipadamente que seu design original pode não ser a melhor abordagem. Se você soubesse disso com antecedência, pode ter escolhido desenvolver um ou dois protótipos de descarte rápido , para explorar as possíveis opções de design e avaliar qual é a maneira mais promissora de seguir. Na prototipagem, você não precisa escrever um código de qualidade de produção e não precisa fazer testes de unidade em todos os cantos (ou em todos), pois seu único foco é aprender, não polir o código.
Agora, perceber que você precisa de prototipagem e experimentos, em vez de iniciar o desenvolvimento do código de produção imediatamente, nem sempre é fácil e nem sempre é possível. Armado com o conhecimento acabado de adquirir, você poderá reconhecer a necessidade de prototipagem da próxima vez. Ou não pode. Mas pelo menos você sabe agora que essa opção deve ser considerada. E isso por si só é um conhecimento importante.
A outra questão é IMHO com a sua percepção. Todos cometemos erros e é tão fácil ver em retrospecto o que deveríamos ter feito de maneira diferente. É assim que aprendemos. Anote seu investimento em testes de unidade como o preço de aprender que a prototipagem pode ser importante e supere-a. Apenas se esforce para não cometer o mesmo erro duas vezes :-)
fonte
O objetivo do TDD é que ele o força a escrever pequenos incrementos de código em pequenas funções , precisamente para evitar esse problema. Se você passou semanas escrevendo código em um domínio e todos os métodos utilitários que você escreveu se tornam inúteis quando você repensa a arquitetura, então seus métodos são quase certamente muito grandes em primeiro lugar. (Sim, eu sei que isso não é exatamente reconfortante agora ...)
fonte
Brooks disse que "planeja jogar um fora; você vai, de qualquer maneira". Parece-me que você está fazendo exatamente isso. Dito isto, você deve escrever seus testes de unidade para testar a unidade de código e não uma grande faixa de código. Esses são testes mais funcionais e, portanto, devem ser aplicados a qualquer implementação interna.
Por exemplo, se eu quiser escrever um solucionador de PDE (equações diferenciais parciais), escreveria alguns testes tentando resolver coisas que posso resolver matematicamente. Esses são meus primeiros testes "unitários" - leia: testes funcionais executados como parte de uma estrutura xUnit. Isso não muda, dependendo do algoritmo usado para resolver o PDE. Tudo o que me importa é o resultado. Os segundos testes de unidade se concentrarão nas funções usadas para codificar o algoritmo e, portanto, seriam específicos do algoritmo - digamos Runge-Kutta. Se eu descobrisse que Runge-Kutta não era adequado, ainda teria os testes de nível superior (incluindo os que mostraram que Runge-Kutta não era adequado). Portanto, a segunda iteração ainda teria muitos dos mesmos testes que o primeiro.
Seu problema talvez seja no design e não necessariamente no código. Mas sem mais detalhes, é difícil dizer.
fonte
Você deve ter em mente que o TDD é um processo iterativo. Escreva um teste pequeno (na maioria dos casos, algumas linhas devem ser suficientes) e execute-o. O teste deve falhar, agora trabalhe diretamente na sua fonte principal e tente implementar a funcionalidade testada para que o teste seja aprovado. Agora comece de novo.
Você não deve tentar escrever todos os testes de uma só vez, porque, como você notou, isso não vai dar certo. Isso reduz o risco de desperdiçar seu tempo escrevendo testes que não serão usados.
fonte
Acho que você mesmo disse: não tinha certeza da sua abordagem antes de começar a escrever todos os seus testes de unidade.
O que aprendi comparando os projetos de TDD da vida real com os quais trabalhei (não muitos, de fato, apenas 3 cobrindo 2 anos de trabalho) com o que aprendi teoricamente, é que o Teste Automatizado! = Teste de Unidade (sem, é claro, ser mutuamente) exclusivo).
Em outras palavras, o T no TDD não precisa ter um U com ele ... Ele é automatizado, mas é menos um teste de unidade (como nas classes e métodos de teste) do que um teste funcional automatizado: está no mesmo nível de granularidade funcional como a arquitetura em que você está trabalhando atualmente. Você inicia de alto nível, com poucos testes e apenas o quadro funcional e, eventualmente, acaba com milhares de UTs e todas as suas classes bem definidas em uma bela arquitetura ...
Os testes de unidade oferecem uma grande ajuda quando você trabalha em equipe, para evitar alterações no código, criando ciclos intermináveis de bugs. Mas nunca escrevi algo tão preciso ao começar a trabalhar em um projeto, antes de ter pelo menos um POC de trabalho global para cada história de usuário.
Talvez seja apenas a minha maneira pessoal de fazer isso. Não tenho experiência suficiente para decidir do zero quais padrões ou estrutura meu projeto terá; portanto, não vou perder meu tempo escrevendo centenas de UTs desde o início ...
De maneira mais geral, a idéia de quebrar tudo e jogar tudo sempre estará lá. Por mais "contínuo" que possamos tentar ser com nossas ferramentas e métodos, às vezes a única maneira de combater a entropia é começar de novo. Mas o objetivo é que, quando isso acontecer, os testes automatizados e de unidade que você implementou, tornem o seu projeto já menos dispendioso do que se não existisse - e, se você encontrar o equilíbrio.
fonte
A fusão de testes de unidade com desenvolvimento orientado a testes é fonte de muita angústia e angústia. Então, vamos revisá-lo mais uma vez:
Em resumo: o teste de unidade tem um foco de implementação, o TDD tem um foco de requisitos. Eles não são a mesma coisa.
fonte
O desenvolvimento orientado a testes destina-se a impulsionar seu desenvolvimento. Os testes que você escreve o ajudam a afirmar a exatidão do código que está escrevendo no momento e aumentam a velocidade de desenvolvimento da primeira linha em diante.
Você parece acreditar que os testes são um fardo e só se destinam ao desenvolvimento incremental posteriormente. Essa linha de pensamento não está alinhada com o TDD.
Talvez você possa compará-lo com a digitação estática: embora seja possível escrever código usando nenhuma informação de tipo estático, a adição de tipo estático ao código ajuda a afirmar certas propriedades do código, liberando a mente e permitindo o foco em estruturas importantes, aumentando a velocidade e eficácia.
fonte
O problema de fazer uma grande refatoração é que você pode e, às vezes, segue um caminho que o leva a perceber que você mordeu mais do que pode mastigar. Refatorações gigantes são um erro. Se o design do sistema é defeituoso em primeiro lugar, a refatoração pode levar você tão longe antes que você precise tomar uma decisão difícil. Deixe o sistema como está e resolva-o ou planeje redesenhar e fazer algumas mudanças importantes.
Existe, no entanto, outro caminho. O benefício real do código de refatoração é tornar as coisas mais simples, fáceis de ler e ainda mais fáceis de manter. Onde você aborda um problema sobre o qual você tem incerteza, gera uma mudança, vai tão longe para ver aonde ele pode levar a fim de aprender mais sobre o problema, depois joga fora o aumento e aplica uma nova refatoração com base no que o aumento ensinei você. O fato é que você realmente pode melhorar seu código apenas com certeza se as etapas forem pequenas e seus esforços de refatoração não ultrapassarem sua capacidade de escrever seus testes primeiro. A tentação é escrever um teste, depois codificar e depois codificar um pouco mais, porque uma solução pode parecer óbvia, mas logo você percebe que sua alteração mudará muitos outros testes; portanto, você deve tomar cuidado para mudar apenas uma coisa de cada vez.
A resposta, portanto, é nunca tornar sua refatoração maior. Passos de bebê. Comece extraindo métodos e tente remover a duplicação. Em seguida, passe para a extração de classes. Cada um em pequenos passos, uma pequena alteração de cada vez. Se você estiver extraindo código, escreva um teste primeiro. Se você estiver removendo o código, remova-o e execute seus testes, e decida se algum dos testes quebrados será mais necessário. Um pequeno passo de bebê de cada vez. Parece que levará mais tempo, mas reduzirá consideravelmente o tempo de refatoração.
A realidade é, no entanto, que cada pico é aparentemente um potencial desperdício de esforço. As alterações de código às vezes não levam a lugar algum, e você se restaura restaurando seu código de seus vcs. Esta é apenas uma realidade do que fazemos no dia a dia. Todo pico que falha não é desperdiçado, no entanto, se ele lhe ensina algo. Todo esforço de refatoração que falhar lhe ensinará que você está tentando fazer muito rapidamente ou que sua abordagem pode estar errada. Isso também não é perda de tempo, se você aprender algo com isso. Quanto mais você faz essas coisas, mais aprende e mais eficiente se tornará. Meu conselho é usá-lo por enquanto, aprender a fazer mais fazendo menos e aceitar que é assim que as coisas provavelmente precisam ser até que você melhore a identificação de quão longe deve dar um pico antes que ele não o leve a lugar nenhum.
fonte
Não tenho certeza sobre o motivo pelo qual sua abordagem foi falha após três dias. Dependendo das suas incertezas em sua arquitetura, você pode considerar mudar sua estratégia de teste:
Se você não tiver certeza sobre o desempenho, pode querer começar com alguns testes de integração que afirmam o desempenho?
Quando a complexidade da API é o que você está investigando, escreva alguns testes de unidade pequenos e reais para descobrir qual seria a melhor maneira de fazer isso. Não se importe em implementar nada, apenas faça com que suas classes retornem valores codificados ou faça com que eles jogem NotImplementedExceptions.
fonte
Para mim, os testes de unidade também são uma ocasião para colocar a interface em uso "real" (bem, tão real quanto os testes de unidade!).
Se sou forçado a fazer um teste, tenho que exercitar meu design. Isso ajuda a manter as coisas sãs (se algo é tão complexo que escrever um teste é um fardo, como será usá-lo?).
Isso não evita alterações no design, mas expõe a necessidade delas. Sim, uma reescrita completa é uma dor. Para (tentar) evitá-lo, costumo configurar (um ou mais) protótipo, possivelmente em Python (com o desenvolvimento final em c ++).
É verdade que você nem sempre tem tempo para todos esses presentes. Esses são precisamente os casos em que você precisará de uma quantidade MAIOR de tempo para atingir seus objetivos ... e / ou manter tudo sob controle.
fonte
Bem-vindo ao circo de desenvolvedores criativos .
Em vez de respeitar toda a maneira 'legal / razoável' de codificar no início,
tente a intuição , acima de tudo, se ela é importante e nova para você e se nenhuma amostra parecer que você deseja:
- Escreva com seu instinto, com coisas que você já conhece , não com sua mente e imaginação.
- E pare.
- Pegue uma lupa e inspecione todas as palavras que você escreve: Você escreve "texto" porque "texto" está próximo de String, mas é necessário "verbo", "adjetivo" ou algo mais preciso, leia novamente e ajuste o método com um novo sentido
. .. ou, você escreveu um pedaço de código pensando no futuro? remova-o
- Corrija, execute outra tarefa (esporte, cultura ou outras coisas fora dos negócios), volte e leia novamente.
- Tudo se encaixa bem,
- Corrija, execute outra tarefa, volte e leia novamente.
- Tudo se encaixa bem, passe para TDD
- Agora tudo está correto, bom
- Tente o benchmark para apontar as coisas a serem otimizadas, faça-o.
O que aparece:
- você escreveu um código respeitando todas as regras
- você obtém uma experiência, uma nova maneira de trabalhar,
- algo mudou em sua mente, você nunca terá medo pela nova configuração.
E agora, se você vir uma UML parecida com a acima, será capaz de dizer
"Chefe, eu começo pelo TDD para isso ...."
é outra coisa nova?
"Chefe, eu tentaria algo antes de decidir como codificarei .."
Atenciosamente PARIS
Claude
fonte