Como as pessoas que fazem TDD lidam com a perda de trabalho ao fazer grandes refatorações?

37

Por um tempo, tenho tentado aprender a escrever testes de unidade para o meu código.

Inicialmente, comecei a fazer o verdadeiro TDD, onde não escrevia nenhum código até ter escrito primeiro um teste com falha.

No entanto, recentemente tive um problema espinhoso para resolver, o que envolveu muito código. Depois de passar um bom par de semanas escrevendo testes e, em seguida, código, cheguei à infeliz conclusão de que toda a minha abordagem não ia funcionar, e eu teria que jogar fora duas semanas de trabalho e começar de novo.

Esta é uma decisão ruim o suficiente para chegar quando você acabou de escrever o código, mas quando você também escreveu várias centenas de testes de unidade, torna-se ainda mais emocionalmente difícil simplesmente jogar tudo fora.

Não posso deixar de pensar que desperdicei 3 ou 4 dias de esforço escrevendo esses testes quando pude apenas juntar o código para a prova de conceito e depois escrevi os testes depois que fiquei feliz com a minha abordagem.

Como as pessoas que praticam o TDD lidam adequadamente com tais situações? Existe um argumento para dobrar as regras em alguns casos ou você sempre escreve os testes primeiro, mesmo quando esse código pode se tornar inútil?

    
por GazTheDestroyer 09.02.2012 / 10:18
fonte

11 respostas

33

Eu sinto que há dois problemas aqui. A primeira é que você não percebeu de antemão que seu projeto original pode não ser a melhor abordagem. Se você soubesse disso com antecedência, talvez tenha escolhido desenvolver um protótipo descartável rápido, ou dois, para explorar as possíveis opções de projeto e avaliar qual é o caminho mais promissor a seguir. Na prototipagem, você não precisa escrever código de qualidade de produção e não precisa testar todos os cantos e recantos (ou de todo), pois seu único foco é o aprendizado, e não o polimento do código.

Agora, perceber que você precisa de protótipos 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 que acabou de ganhar, você poderá reconhecer a necessidade de prototipagem na próxima vez. Ou não pode. Mas pelo menos você sabe agora que esta opção deve ser considerada. E isso em si é um conhecimento importante.

A outra questão é IMHO com sua percepção. Todos nós cometemos erros, e é tão fácil ver em retrospecto o que deveríamos ter feito diferente. É assim que aprendemos. Anote seu investimento em testes de unidade, pois o preço de aprender que a prototipagem pode ser importante e superá-lo. Apenas se esforce para não cometer o mesmo erro duas vezes: -)

    
por 09.02.2012 / 11:33
fonte
8

O ponto de TDD é que ele força você 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 cada método de utilitário que você escreveu se torna inútil quando você repensa a arquitetura, então seus métodos quase certamente são muito grandes em primeiro lugar. (Sim, estou ciente de que isso não é exatamente reconfortante agora ...)

    
por 09.02.2012 / 10:31
fonte
6

Brooks disse "planeje jogar um fora; você vai, de qualquer forma". Parece-me que você está fazendo exatamente isso. Dito isso, 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 executados em qualquer implementação interna.

Por exemplo, se eu quiser escrever um solucionador PDE (equações diferenciais parciais), eu escreveria alguns testes tentando resolver coisas que eu possa resolver matematicamente. Esses são meus primeiros testes de "unidade" - leia-se: testes funcionais são executados como parte de uma estrutura xUnit. Isso não vai mudar dependendo do algoritmo que eu uso para resolver o PDE. Tudo o que me importa é o resultado. Os testes da segunda unidade se concentrarão nas funções usadas para codificar o algoritmo e, portanto, seriam específicas do algoritmo - digamos, Runge-Kutta. Se eu descobrisse que o Runge-Kutta não era adequado, então eu ainda teria esses testes de nível superior (incluindo os que mostravam que o Runge-Kutta não era adequado). Assim, a segunda iteração ainda teria muitos dos mesmos testes que o primeiro.

Seu problema talvez seja de design e não necessariamente de código. Mas sem mais detalhes, é difícil dizer.

    
por 09.02.2012 / 11:22
fonte
5

Você deve ter em mente que o TDD é um processo interativo. Escreva um pequeno teste (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ê percebeu, isso não vai dar certo. Isso reduz o risco de perder seu tempo escrevendo testes que não serão usados.

    
por 09.02.2012 / 10:47
fonte
4

Eu acho que você mesmo disse: você não tinha certeza sobre sua abordagem antes de começar a escrever todos os seus testes de unidade.

A coisa que eu aprendi comparando os projetos TDD da vida real com os quais eu trabalhei (não muitos na verdade, apenas 3 cobrindo 2 anos de trabalho) com o que eu aprendi teoricamente, é que Testes Automatizados! = Teste Unitário (sem claro que é 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 no teste de classes e métodos) do que um teste funcional automatizado: está em o mesmo nível de granularidade funcional da arquitetura em que você está trabalhando atualmente. Você começa de alto nível, com poucos testes e apenas a grande figura funcional, e somente eventualmente você acaba com milhares de UTs, e todas as suas classes são bem definidas em uma bela arquitetura ...

Os testes de unidade oferecem uma grande ajuda quando você trabalha em equipe, para evitar alterações de código, criando ciclos intermináveis de erros. Mas eu nunca escrevi nada tão preciso quando comecei 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 minha maneira pessoal de fazer isso. Eu não tenho a experiência suficiente para decidir do zero quais padrões ou estrutura meu projeto terá, então, de fato, eu não vou perder meu tempo escrevendo 100s de UTs desde o começo ...

Em geral, a ideia de quebrar tudo e jogar tudo estará sempre lá. Por mais "contínuos" que possamos tentar ser com nossas ferramentas e métodos, às vezes a única maneira de lutar contra a entropia é começar de novo. Mas o objetivo é que, quando isso acontecer, os testes automatizados e de unidade que você implementou tornem seu projeto já menos custoso do que se não houvesse - e, se você encontrar o equilíbrio.

    
por 09.02.2012 / 13:20
fonte
4
How do people who practice TDD properly handle such situations?
  1. considerando quando criar um protótipo vs quando codificar
  2. percebendo que o teste de unidade não é o mesmo que TDD
  3. por escritos TDD testa para verificar um recurso / história, não uma unidade funcional

A fusão de testes unitários com desenvolvimento orientado a testes é fonte de muita angústia e infortúnio. Então, vamos revisá-lo mais uma vez:

  • teste unitário diz respeito à verificação de cada módulo e função individual na implementação ; em UT você verá uma ênfase em coisas como métricas de cobertura de código e testes que são executados muito rapidamente
  • O desenvolvimento orientado a testes preocupa-se em verificar cada recurso / matéria nos requisitos ; no TDD, você verá uma ênfase em coisas como escrever primeiro o teste, garantir que o código escrito não exceda o escopo pretendido e refatorar a qualidade

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.

    
por 09.02.2012 / 16:57
fonte
3

O desenvolvimento orientado a testes tem como objetivo direcionar seu desenvolvimento. Os testes que você escreve ajudam a afirmar a exatidão do código que você está escrevendo no momento e aumentam a velocidade de desenvolvimento a partir da primeira linha.

Você parece acreditar que os testes são um fardo e só servem para o desenvolvimento incremental mais tarde. Essa linha de pensamento não está alinhada com o TDD.

Talvez você possa compará-lo com tipagem estática: embora seja possível escrever código usando nenhuma informação de tipo estático, adicionar o 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 assim a velocidade e a eficácia.

    
por 09.02.2012 / 11:34
fonte
2

O problema em fazer uma grande refatoração é que você pode e às vezes seguirá um caminho que o levará a perceber que você mordeu mais do que pode mastigar. Refatorações gigantes são um erro. Se o design do sistema for defeituoso, a refatoração só poderá levá-lo até certo ponto antes de você precisar tomar uma decisão difícil. Deixe o sistema como está e contorne-o ou planeje redesenhar e fazer algumas alterações importantes.

Existe, no entanto, outro caminho. O benefício real do código de refatoração é tornar as coisas mais simples, mais fáceis de ler e mais fáceis de manter. Quando você aborda um problema sobre o qual você tem incerteza, você atinge uma mudança, vai até onde ele pode levar para aprender mais sobre o problema, depois joga fora o pico e aplica uma nova refatoração baseada no que o pico ensinei você. A única coisa é que você pode realmente melhorar seu código 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, depois codificar um pouco mais porque uma solução pode parecer óbvia, mas logo você percebe que sua mudança vai mudar muitos outros testes, então você precisa ter cuidado para mudar apenas uma coisa de cada vez.

A resposta, portanto, é nunca tornar sua refatoração importante. Passos de bebê. Comece extraindo métodos e, em seguida, procure remover a duplicação. Em seguida, mova para extrair classes. Cada um em pequenos passos, uma pequena alteração de cada vez. SE você está extraindo código, escreva um teste primeiro. Se você estiver removendo o código, remova-o, execute seus testes e decida se algum dos testes quebrados será mais necessário. Um pequeno bebê passo de cada vez. Parece que levará mais tempo, mas na verdade diminuirá consideravelmente o tempo de refatoração.

A realidade é que cada pico é aparentemente um potencial desperdício de esforço. Mudanças de código às vezes não chegam a lugar algum, e você se encontra restaurando seu código de seus vcs. Esta é apenas uma realidade do que fazemos no dia a dia. Cada pico que falha não é desperdiçado, no entanto, se ele ensina alguma coisa. Todo esforço de refatoração que falhar lhe ensinará que você está tentando fazer muito ou muito rápido, ou que sua abordagem pode estar errada. Isso também não é uma perda de tempo se você aprender alguma coisa com isso. Quanto mais você fizer isso, mais aprenderá e mais eficiente se tornará. Meu conselho é apenas usá-lo por agora, aprender a fazer mais fazendo menos e aceitar que isso é exatamente o que as coisas provavelmente precisam ser até que você consiga identificar até que ponto levar um pico antes que ele não leve a lugar nenhum. / p>     
por 09.02.2012 / 22:49
fonte
1

Eu não tenho certeza sobre o motivo pelo qual sua abordagem acabou falhando após 3 dias. Dependendo das suas incertezas em sua arquitetura, você pode pensar em alterar sua estratégia de testes:

  • Se você não tiver certeza sobre o desempenho, talvez queira começar com alguns testes de integração que asseguram 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 preocupe em implementar nada, apenas faça suas classes retornarem valores codificados ou faça com que eles descartem NotImplementedExceptions.

por 09.02.2012 / 13:33
fonte
0

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 eu for forçado a fazer um teste, tenho que exercitar meu design. Isso ajuda a manter as coisas saudáveis (se algo é tão complexo que escrever um teste é um fardo, como será usá-lo?).

Isso não evita mudanças no design, mas expõe a necessidade deles. Sim, uma reescrita completa é uma dor. Para (tentar) evitá-lo eu normalmente configuro (um ou mais) protótipo, possivelmente em Python (com o desenvolvimento final em c ++).

Concedido, você nem sempre tem tempo para todas essas coisas. Esses são precisamente os casos em que você precisará de um tempo LARGER maior para atingir suas metas ... e / ou manter tudo sob controle.

    
por 13.02.2012 / 19:40
fonte
0

Bem-vindo ao circo de criadores de conteúdo .


Em vez de respeitar todas as formas "legais / razoáveis" de codificar no começo,
 tente intuição , acima de tudo, se é importante e novo para você e se nenhuma amostra parece que você quer:

- Escreva com o seu instinto, de coisas que você já conhece, não com o seu mental e imaginação.
- E pare.
- Pegue uma lupa e inspecione todas as palavras que você escreve: Você escreve "texto" porque "texto" está perto de String, mas "verbo", "adjetivo" ou algo mais preciso é necessário, 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
- Correto, faça outra tarefa (esporte, cultura ou outras coisas fora dos negócios),  volte e leia novamente.
- Tudo bem, passe para a UML
- Corrija, faça outra tarefa, volte e leia novamente.
- Tudo bem, passe para o TDD
- Agora tudo está correto, bom - Tente benchmark para apontar as coisas a serem otimizadas, faça isso.

O que aparecem:
- você escreveu um código respeitando todas as regras
- você tem uma experiência, uma nova maneira de trabalhar,
- algo mudar em sua mente, você nunca terá medo por nova configuração.

E agora, se você ver uma UML parecida com a acima, você poderá dizer "Chefe, eu começo com TDD para isso ..."
é outra coisa nova?
"Chefe, eu tentaria algo antes de decidir como codifico .."

Melhores cumprimentos de PARIS
Claude

    
por 15.02.2012 / 10:33
fonte