Em muitos casos, posso ter uma classe existente com algum comportamento:
class Lion
{
public void Eat(Herbivore herbivore) { ... }
}
... e eu tenho um teste de unidade ...
[TestMethod]
public void Lion_can_eat_herbivore()
{
var herbivore = buildHerbivoreForEating();
var test = BuildLionForTest();
test.Eat(herbivore);
Assert.IsEaten(herbivore);
}
Agora, o que acontece é que preciso criar uma classe Tiger com comportamento idêntico ao do Lion:
class Tiger
{
public void Eat(Herbivore herbivore) { ... }
}
... e como quero o mesmo comportamento, preciso executar o mesmo teste, faço algo assim:
interface IHerbivoreEater
{
void Eat(Herbivore herbivore);
}
... e refatoro meu teste:
[TestMethod]
public void Lion_can_eat_herbivore()
{
IHerbivoreEater_can_eat_herbivore(BuildLionForTest);
}
public void IHerbivoreEater_can_eat_herbivore(Func<IHerbivoreEater> builder)
{
var herbivore = buildHerbivoreForEating();
var test = builder();
test.Eat(herbivore);
Assert.IsEaten(herbivore);
}
... e depois adiciono outro teste para minha nova Tigerclasse:
[TestMethod]
public void Tiger_can_eat_herbivore()
{
IHerbivoreEater_can_eat_herbivore(BuildTigerForTest);
}
... e então refatoro minhas classes Lione Tiger(geralmente por herança, mas às vezes por composição):
class Lion : HerbivoreEater { }
class Tiger : HerbivoreEater { }
abstract class HerbivoreEater : IHerbivoreEater
{
public void Eat(Herbivore herbivore) { ... }
}
... e está tudo bem. No entanto, como a funcionalidade agora está na HerbivoreEaterclasse, parece que há algo errado em ter testes para cada um desses comportamentos em cada subclasse. No entanto, são as subclasses que estão realmente sendo consumidas, e é apenas um detalhe da implementação que elas compartilham comportamentos sobrepostos ( Lionse Tigerspodem ter usos finais totalmente diferentes, por exemplo).
Parece redundante testar o mesmo código várias vezes, mas há casos em que a subclasse pode e substitui a funcionalidade da classe base (sim, isso pode violar o LSP, mas vamos ser sinceros, IHerbivoreEateré apenas uma interface de teste conveniente - é pode não ser importante para o usuário final). Então esses testes têm algum valor, eu acho.
O que outras pessoas fazem nessa situação? Você acabou de passar seu teste para a classe base ou todas as subclasses para o comportamento esperado?
EDIT :
Com base na resposta do @pdr, acho que devemos considerar isso: o IHerbivoreEateré apenas um contrato de assinatura de método; não especifica comportamento. Por exemplo:
[TestMethod]
public void Tiger_eats_herbivore_haunches_first()
{
IHerbivoreEater_eats_herbivore_haunches_first(BuildTigerForTest);
}
[TestMethod]
public void Cheetah_eats_herbivore_haunches_first()
{
IHerbivoreEater_eats_herbivore_haunches_first(BuildCheetahForTest);
}
[TestMethod]
public void Lion_eats_herbivore_head_first()
{
IHerbivoreEater_eats_herbivore_head_first(BuildLionForTest);
}
Eatcomportamento na classe base, todas as subclasses devem exibir o mesmo Eatcomportamento. No entanto, estou falando de duas classes relativamente independentes que compartilham um comportamento. Considere, por exemplo, o Flycomportamento de Bricke Personque, podemos assumir, exibem um comportamento de vôo semelhante, mas não faz sentido necessariamente que eles derivem de uma classe base comum.
Animalclasse que contenhaEat? Todos os animais comem e, portanto, a classeTigereLionpoderia herdar do animal.