Se eu tiver uma função no meu código que seja como:
class Employee{
public string calculateTax(string name, int salary)
{
switch (name)
{
case "Chris":
doSomething($salary);
case "David":
doSomethingDifferent($salary);
case "Scott":
doOtherThing($salary);
}
}
Normalmente eu refatoraria isso para usar o Ploymorphism usando uma classe de fábrica e um padrão de estratégia:
public string calculateTax(string name)
{
InameHandler nameHandler = NameHandlerFactory::getHandler(name);
nameHandler->calculateTax($salary);
}
Agora, se eu estivesse usando TDD, teria alguns testes que funcionam no original calculateTax()
antes de refatorar.
ex:
calculateTax_givenChrisSalaryBelowThreshold_Expect111(){}
calculateTax_givenChrisSalaryAboveThreshold_Expect111(){}
calculateTax_givenDavidSalaryBelowThreshold_Expect222(){}
calculateTax_givenDavidSalaryAboveThreshold_Expect222(){}
calculateTax_givenScottSalaryBelowThreshold_Expect333(){}
calculateTax_givenScottSalaryAboveThreshold_Expect333(){}
Após a refatoração, terei uma classe Factory NameHandlerFactory
e pelo menos três implementações InameHandler
.
Como devo proceder para refatorar meus testes? Devo excluir o teste de unidade claculateTax()
de EmployeeTests
e criar uma classe de teste para cada implementação de InameHandler
?
Devo testar a classe Factory também?
salary
para a funçãocalculateTax()
foi adicionada. Dessa forma, acho que duplicarei o código de teste para a função original e as 3 implementações da classe de estratégia.Começarei dizendo que não sou especialista em TDD ou teste de unidade, mas aqui está como eu testaria isso (usarei código pseudo-semelhante):
Então, eu testaria se o
calculateTax()
método da classe empregado pede corretamenteNameHandlerFactory
aNameHandler
e chama ocalculateTax()
método retornadoNameHandler
.fonte
Employee.calculateTax()
método. Dessa forma, você não precisa adicionar testes de funcionários extras ao introduzir um novo NameHandler.Você está fazendo uma aula (funcionário que faz tudo) e fazendo 3 grupos de aulas: a fábrica, o funcionário (que contém apenas uma estratégia) e as estratégias.
Então faça 3 grupos de testes:
É claro que você pode fazer testes automatizados para toda a shebang, mas agora são mais como testes de integração e devem ser tratados como tal.
fonte
Antes de escrever qualquer código, eu começaria com um teste para uma fábrica. Zombando das coisas de que preciso, eu me forçaria a pensar nas implementações e nos casos de uso.
Então, eu implementaria uma fábrica e continuaria com um teste para cada implementação e, finalmente, as próprias implementações para esses testes.
Finalmente, eu removia os testes antigos.
fonte
Minha opinião é que você não deve fazer nada, o que significa que não deve adicionar novos testes.
Enfatizo que essa é uma opinião e, na verdade, depende da maneira como você percebe as expectativas do objeto. Você acha que o usuário da classe gostaria de fornecer uma estratégia para o cálculo de impostos? Se ele não se importa, os testes devem refletir isso, e o comportamento refletido nos testes de unidade deve ser que eles não se importem se a classe começou a usar um objeto de estratégia para calcular impostos.
Na verdade, eu tive esse problema várias vezes ao usar o TDD. Penso que o principal motivo é que um objeto de estratégia não é uma dependência natural, em vez de dizer uma dependência de limite arquitetural como um recurso externo (um arquivo, um banco de dados, um serviço remoto, etc.). Como não é uma dependência natural, geralmente não baseio o comportamento da minha classe nessa estratégia. Meu instinto é que só devo mudar meus testes se as expectativas da minha classe tiverem mudado.
Há um ótimo post do tio Bob, que fala exatamente sobre esse problema ao usar o TDD.
Eu acho que a tendência de testar cada classe separada é o que está matando o TDD. Toda a beleza do TDD é que você usa testes para estimular esquemas de design e não vice-versa.
fonte