Qual é o melhor curso de ação no TDD se, depois de implementar a lógica corretamente, o teste ainda falhar (porque há um erro no teste)?
Por exemplo, suponha que você gostaria de desenvolver a seguinte função:
int add(int a, int b) {
return a + b;
}
Suponha que o desenvolvamos nas seguintes etapas:
Teste de gravação (nenhuma função ainda):
// test1 Assert.assertEquals(5, add(2, 3));
Resultados em erro de compilação.
Escreva uma implementação de função fictícia:
int add(int a, int b) { return 5; }
Resultado:
test1
passa.Adicione outro caso de teste:
// test2 -- notice the wrong expected value (should be 11)! Assert.assertEquals(12, add(5, 6));
Resultado:
test2
falha,test1
ainda passa.Escreva implementação real:
int add(int a, int b) { return a + b; }
Resultado:
test1
ainda passa,test2
ainda falha (desde11 != 12
).
Nesse caso em particular: seria melhor:
- correto
test2
e veja que agora passa ou - exclua a nova parte da implementação (ou seja, volte para a etapa 2 acima), corrija
test2
e deixe falhar e, em seguida, reintroduza a implementação correta (etapa 4, acima).
Ou existe alguma outra maneira mais inteligente?
Embora eu entenda que o problema de exemplo é bastante trivial, estou interessado no que fazer no caso genérico, que pode ser mais complexo do que a adição de dois números.
EDIT (Em resposta à resposta de @Thomas Junk):
O foco desta pergunta é o que o TDD sugere nesse caso, e não a "melhor prática universal" para obter bons códigos ou testes (que podem ser diferentes do modo TDD).
Respostas:
O absolutamente crítico é que você veja o teste passar e falhar.
Se você exclui o código para fazer com que o teste falhe, reescreva-o ou espreite-o para a área de transferência apenas para colá-lo mais tarde, não importa. O TDD nunca disse que era preciso redigitar nada. Ele quer saber que o teste passa apenas quando deveria passar e falha somente quando deveria falhar.
Ver o teste passar e falhar é como você o testará. Nunca confie em um teste que nunca viu fazer as duas coisas.
A refatoração contra a barra vermelha fornece etapas formais para refatorar um teste de trabalho:
No entanto, não estamos refatorando um teste de trabalho. Temos que transformar um teste de buggy. Uma preocupação é o código que foi introduzido enquanto apenas este teste o abordava. Esse código deve ser revertido e reintroduzido após a correção do teste.
Se não for esse o caso, e a cobertura do código não for uma preocupação devido a outros testes que cobrem o código, você pode transformar o teste e apresentá-lo como um teste verde.
Aqui, o código também está sendo revertido, mas apenas o suficiente para causar falha no teste. Se isso não for suficiente para cobrir todo o código introduzido, enquanto apenas coberto pelo teste de buggy, precisamos de uma reversão maior do código e de mais testes.
Introduzir um teste verde
Quebrar o código pode ser comentar o código ou movê-lo para outro lugar apenas para colá-lo novamente mais tarde. Isso nos mostra o escopo do código que o teste cobre.
Nas duas últimas corridas, você volta ao ciclo verde vermelho normal. Você está apenas colando em vez de digitar para descompactar o código e fazer o teste passar. Portanto, certifique-se de colar apenas o suficiente para fazer o teste passar.
O padrão geral aqui é ver a cor do teste mudar da maneira que esperamos. Observe que isso cria uma situação em que você faz um teste ecológico não confiável brevemente. Tenha cuidado para ser interrompido e esquecer onde você está nessas etapas.
Meus agradecimentos a RubberDuck pelo link Embracing the Red Bar .
fonte
Qual é o objetivo geral que você deseja alcançar?
Fazendo bons testes?
Fazendo a implementação correta ?
Fazendo TTD religiosamente certo ?
Nenhuma das acima?
Talvez você pense demais em seu relacionamento com testes e testes.
Os testes não garantem a correção de uma implementação. Ter todos os testes aprovados não diz nada sobre se o seu software faz o que deveria; não faz declarações essencialistas sobre o seu software.
Tomando o seu exemplo:
A implementação "correta" da adição seria o código equivalente a
a+b
. E enquanto o seu código fizer isso, você diria que o algoritmo está correto no que faz e está implementado corretamente .À primeira vista , nós dois concordamos que esta é a implementação de uma adição.
Mas o que estamos fazendo realmente não está dizendo, que este código é a implementação de
addition
que apenas comporta até certo ponto como uma: pensar de estouro de inteiro .O excesso de número inteiro acontece no código, mas não no conceito de
addition
. Então: seu código se comporta até certo ponto como o conceito deaddition
, mas não éaddition
.Este ponto de vista bastante filosófico tem várias consequências.
E uma é, você poderia dizer, que os testes nada mais são do que suposições do comportamento esperado do seu código. Ao testar seu código, você pode (talvez) nunca ter certeza de que sua implementação está correta , o melhor que você poderia dizer é que suas expectativas sobre os resultados que seu código fornece foram ou não atendidas; seja, que seu código esteja errado, seja, que seu teste esteja errado ou seja, que ambos estejam errados.
Testes úteis ajudam você a fixar suas expectativas sobre o que o código deve fazer: desde que eu não mude minhas expectativas e desde que o código modificado me dê o resultado esperado, posso ter certeza de que as suposições que fiz sobre os resultados parecem dar certo.
Isso não ajuda quando você fez as suposições erradas; mas ei! pelo menos evita a esquizofrenia: esperando resultados diferentes quando não deveria haver nenhum.
tl; dr
Seus testes são suposições sobre o comportamento do código. Se você tiver boas razões para pensar que sua implementação está correta, corrija o teste e verifique se essa suposição é válida.
fonte
datatype
é claramente a escolha errada. Um teste revelaria que: sua expectativa seria "funciona para grandes números" e, em muitos casos, não é atendida. Então a questão seria como lidar com esses casos. Eles são casos de canto? Quando sim, como lidar com eles? Talvez algumas cláusulas de pensão ajudem a evitar uma bagunça maior. A resposta é vinculada ao contexto.Você precisa saber que o teste falhará se a implementação estiver errada, o que não é o mesmo que passar se a implementação estiver correta. Portanto, você deve colocar o código novamente em um estado em que espera que falhe antes de corrigir o teste e verifique se ele falhou pelo motivo esperado (ie
5 != 12
), em vez de algo que não previu.fonte
assertTrue(5 == add(2, 3))
fornece uma saída menos útil do queassertEqual(5, add(2, 3))
mesmo que ambos estejam testando a mesma coisa).Nesse caso em particular, se você alterar o 12 para o 11 e o teste agora passar, acho que você fez um bom trabalho ao testar o teste e a implementação, para que não haja muita necessidade de passar por outras etapas.
No entanto, o mesmo problema pode surgir em situações mais complexas, como quando você tem um erro no seu código de instalação. Nesse caso, depois de corrigir seu teste, você provavelmente deve tentar alterar sua implementação de forma a fazer com que esse teste específico falhe e depois reverter a mutação. Se a reversão da implementação for a maneira mais fácil de fazer isso, tudo bem. No seu exemplo, você pode mudar
a + b
paraa + a
oua * b
.Como alternativa, se você pode alterar a asserção levemente e ver o teste falhar, isso pode ser bastante eficaz no teste do teste.
fonte
Eu diria que esse é o caso do seu sistema de controle de versão favorito:
Faça a correção do teste, mantendo as alterações de código no diretório de trabalho.
Confirme com uma mensagem correspondente
Fixed test ... to expect correct output
.Com
git
, isso pode exigir o uso degit add -p
se teste e implementação estiverem no mesmo arquivo; caso contrário, você poderá obviamente apenas preparar os dois arquivos separadamente.Confirme o código de implementação.
Volte no tempo para testar a confirmação feita na etapa 1, certificando-se de que o teste realmente falhe .
Veja bem, dessa maneira, você não confia nas suas proezas de edição para mover seu código de implementação para fora do caminho enquanto você testa seu teste com falha. Você emprega seu VCS para salvar seu trabalho e garantir que o histórico gravado do VCS inclua corretamente o teste de reprovação e aprovação.
fonte