Como lidar com a aprovação no teste desde o início no TDD


8

Estou tentando praticar o TDD no meu projeto pessoal e me pergunto como lidar com a situação quando, após adicionar um novo teste, ele passa desde o início com base na implementação existente?

Por um lado, o novo teste pode fornecer documentação adicional do design e proteção contra violação acidental de suposições.

Por outro lado, se o teste for aprovado sem nenhuma alteração no código, é "suspeito" se ele realmente testa ou não o que deveria.

Basicamente, o que pode ser feito para confirmar a correção do teste que afirma o comportamento já implementado?


5
minha abordagem favorita é o teste de mutação, eu alterar o código testado e verificar se inicia teste não
mosquito

2
@gnat - Eu acredito que há alguma sobreposição, mas eu não acho que seja a mesma pergunta. Aqui, estou perguntando especificamente sobre o TDD e acredito que "Se você está se encontrando nessa situação, está fazendo o TDD errado porque ..." seria uma resposta válida.
Agrzes

5
não importa, porque você não está fazendo TDD já quando você escreve teste contra o já existente (e funcionando corretamente) implementação
mosquito

2
"e o que você faz quando encontra isso?" Executo o novo teste (somente isso) com uma ferramenta de cobertura e verifico se o caminho esperado é executado. A reversão é apenas uma opção se você fizer o projeto para treinamento. BTW: o Dia Global de Coderetreat é uma grande oportunidade para praticar TDD ...
Timothy Truckle

2
@ Solomonoff'sSecret pessoalmente, não confio em nenhum teste que não vi falhar.
precisa saber é o seguinte

Respostas:


13

Passar em um teste a partir do momento em que você o escreveu pode ser um problema com seu processo TDD, mas isso não significa que ele esteja errado por si só.

Seu teste pode passar por coincidência.

Digamos que você tenha um método que retorne o custo de sacar dinheiro usando um caixa eletrônico. Agora, você é solicitado a adicionar a nova regra de que, se o proprietário do cartão tiver mais de 60 anos, o custo será zero. Portanto, nós o testamos, esperando que falhe:

assertTrue(ATM.withdrawalCost(clientOver60) == 0)

Você pode esperar que isso falhe. Mas isso passa, já que o cliente passa a ser um cliente VIP, que tem saques gratuitos. Agora você pode voltar para o método retiradaalCost e modificá-lo para que falhe, mas isso não faz muito sentido. Escreva um novo teste para mostrar que seu código está errado:

assertTrue(ATM.withdrawalCost(nonVIPclientOver60) == 0)

Agora ele falha, você codifica até que passe e repita até terminar.

Você deve apagar o teste, pois não faz diferença? Não! Ele descreve a funcionalidade esperada do método de retirada ATMCost. Se você apagá-lo e algum dia o custo de retirada de custo 0 para clientes VIP mudar, a primeira afirmação ainda deve ser verdadeira.

Dito isto, para o TDD adequado, você não deve codificar as coisas antes dos testes e depois testar as coisas que sabe que serão aprovadas. Não considero esse o caso que você está perguntando.

Acredito que o ciclo de falha no código-passe deve evitar o desenvolvimento "Vou escrever esses 3 testes que falharão e os 2 que passarão porque eu já o codifiquei sabendo o que o teste seria". Você deve saber que algumas pessoas podem sentir o contrário. Ouça suas razões, elas também podem ser válidas.


10

Os testes aprovados desde o início geralmente ocorrem quando alguém implementa algo de uma maneira mais geral do que o necessário para os testes em questão. Isso é bastante normal : os testes de unidade podem fornecer apenas um número pequeno e finito de valores de entrada para uma determinada função, mas a maioria das funções é escrita para uma enorme variedade de possíveis valores de entrada. Geralmente, uma implementação projetada especificamente para os casos de teste atuais seria mais complicada do que uma solução mais geral. Se for esse o caso, seria complicado e propenso a erros projetar artificialmente o código de forma a funcionar apenas nos casos de teste e falhar em todo o resto.

Por exemplo, digamos que você precise de uma função para retornar o mínimo de alguns valores de uma determinada matriz. Você fez uma implementação, conduzida por um teste com uma matriz contendo apenas um ou dois valores. Mas, em vez de implementá-lo de maneira complicada, fazendo comparações em diferentes elementos (talvez apenas os dois primeiros), você chama uma função de mínimo de array da biblioteca padrão do seu ecossistema de idiomas e torna a implementação uma linha única . Quando você agora decide adicionar um teste com uma matriz de cinco elementos, o teste provavelmente será aprovado desde o início.

Mas como você sabe que o teste não é "verde" por causa de um bug no próprio teste? Uma maneira simples e direta de abordar isso é fazer uma modificação temporária no sujeito em teste para que o teste falhe. Por exemplo, você pode adicionar intencionalmente uma linha if (array.size()==5) return 123à sua função. Agora seu teste de cinco elementos falhará, então você sabe

  • o teste é executado
  • a chamada Assert no teste é executada
  • a chamada Afirmar no teste valida a coisa certa

o que deve lhe dar alguma confiança no teste. Depois de ver o teste falhar, desfaça a modificação e o teste deverá passar novamente.

Como alternativa, você pode modificar o resultado esperado de um teste: digamos que seu teste aprovado contenha uma asserção como

 int result = Subject.UnderTest(...);
 Assert.AreEqual(1,result);

então você pode editar o teste e substituir o "1" por "2". Quando o teste falha (conforme o esperado), você sabe que funciona como deveria e pode desfazer a substituição e ver se o teste agora fica verde. O risco de introduzir um bug no teste por esse tipo de substituição é muito pequeno; portanto, isso provavelmente é aceitável para a maioria dos casos do mundo real.

Uma maneira diferente, talvez discutível, é definir um ponto de interrupção no teste e usar um depurador para percorrê-lo. Isso também deve dar a você certa confiança de que o código do teste é realmente executado e oferece a possibilidade de validar o caminho através do teste por inspeção passo a passo. No entanto, é preciso ter muito cuidado para não ignorar erros em um caminho de código especificamente para um teste com falha. Para testes complexos, considere fazer as duas coisas - fazendo com que falhe artificialmente e use um depurador para inspecioná-lo.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.