In vielen Fällen habe ich möglicherweise eine vorhandene Klasse mit einem gewissen Verhalten:
class Lion
{
public void Eat(Herbivore herbivore) { ... }
}
... und ich habe einen Unit Test ...
[TestMethod]
public void Lion_can_eat_herbivore()
{
var herbivore = buildHerbivoreForEating();
var test = BuildLionForTest();
test.Eat(herbivore);
Assert.IsEaten(herbivore);
}
Was jetzt passiert, ist, dass ich eine Tiger-Klasse mit einem identischen Verhalten wie das des Löwen erstellen muss:
class Tiger
{
public void Eat(Herbivore herbivore) { ... }
}
... und da ich das gleiche Verhalten möchte, muss ich den gleichen Test ausführen, mache ich so etwas:
interface IHerbivoreEater
{
void Eat(Herbivore herbivore);
}
... und ich überarbeite meinen Test:
[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);
}
... und dann füge ich einen weiteren Test für meine neue TigerKlasse hinzu:
[TestMethod]
public void Tiger_can_eat_herbivore()
{
IHerbivoreEater_can_eat_herbivore(BuildTigerForTest);
}
... und dann überarbeite ich meine Lionund TigerKlassen (normalerweise durch Vererbung, manchmal aber auch durch Komposition):
class Lion : HerbivoreEater { }
class Tiger : HerbivoreEater { }
abstract class HerbivoreEater : IHerbivoreEater
{
public void Eat(Herbivore herbivore) { ... }
}
... und alles ist gut. Da sich die Funktionalität jetzt in der HerbivoreEaterKlasse befindet, scheint es nun etwas falsch zu sein, Tests für jedes dieser Verhaltensweisen in jeder Unterklasse durchzuführen. Es sind jedoch die Unterklassen, die tatsächlich verwendet werden, und es ist nur ein Implementierungsdetail, dass sie zufällig überlappende Verhaltensweisen aufweisen ( Lionsund Tigersbeispielsweise völlig unterschiedliche Endverwendungen haben können).
Es scheint überflüssig, denselben Code mehrmals zu testen, aber es gibt Fälle, in denen die Unterklasse die Funktionalität der Basisklasse überschreiben kann und tut (ja, es könnte den LSP verletzen, aber seien wir ehrlich, es IHerbivoreEaterist nur eine praktische Testschnittstelle - es ist ist für den Endbenutzer möglicherweise nicht wichtig). Ich denke, diese Tests haben einen gewissen Wert.
Was machen andere Leute in dieser Situation? Verschieben Sie Ihren Test einfach in die Basisklasse oder testen Sie alle Unterklassen auf das erwartete Verhalten?
EDIT :
Basierend auf der Antwort von @pdr denke ich, dass wir dies berücksichtigen sollten: Das IHerbivoreEaterist nur ein Methodensignaturvertrag; Es gibt kein Verhalten an. Zum Beispiel:
[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);
}
Eatsollten alle Unterklassen dasselbe EatVerhalten aufweisen , wenn Sie das Verhalten in die Basisklasse einfügen . Ich spreche jedoch von zwei relativ unabhängigen Klassen, die zufällig ein Verhalten teilen. Betrachten Sie zum Beispiel das FlyVerhalten von Brickund Persondas, wie wir annehmen können, ein ähnliches Flugverhalten aufweist, aber es ist nicht unbedingt sinnvoll, sie von einer gemeinsamen Basisklasse abzuleiten.
AnimalKlasse haben, die enthältEat? Alle Tiere fressen, und daher könnte die KlasseTigerundLionvom Tier erben.