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 Tiger
Klasse hinzu:
[TestMethod]
public void Tiger_can_eat_herbivore()
{
IHerbivoreEater_can_eat_herbivore(BuildTigerForTest);
}
... und dann überarbeite ich meine Lion
und Tiger
Klassen (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 HerbivoreEater
Klasse 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 ( Lions
und Tigers
beispielsweise 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 IHerbivoreEater
ist 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 IHerbivoreEater
ist 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);
}
Eat
sollten alle Unterklassen dasselbe Eat
Verhalten 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 Fly
Verhalten von Brick
und Person
das, wie wir annehmen können, ein ähnliches Flugverhalten aufweist, aber es ist nicht unbedingt sinnvoll, sie von einer gemeinsamen Basisklasse abzuleiten.
Animal
Klasse haben, die enthältEat
? Alle Tiere fressen, und daher könnte die KlasseTiger
undLion
vom Tier erben.