多くの場合、私はいくつかの動作を持つ既存のクラスがあるかもしれません:
class Lion
{
public void Eat(Herbivore herbivore) { ... }
}
...そして単体テストがあります...
[TestMethod]
public void Lion_can_eat_herbivore()
{
var herbivore = buildHerbivoreForEating();
var test = BuildLionForTest();
test.Eat(herbivore);
Assert.IsEaten(herbivore);
}
さて、私はライオンの行動と同じ行動をするTigerクラスを作成する必要があります:
class Tiger
{
public void Eat(Herbivore herbivore) { ... }
}
...そして私は同じ動作をしたいので、同じテストを実行する必要があります、私はこのようなことをします:
interface IHerbivoreEater
{
void Eat(Herbivore herbivore);
}
...そして私は私のテストをリファクタリングします:
[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);
}
...次に、新しいTiger
クラスに別のテストを追加します。
[TestMethod]
public void Tiger_can_eat_herbivore()
{
IHerbivoreEater_can_eat_herbivore(BuildTigerForTest);
}
...そして、私のクラスLion
とTiger
クラスをリファクタリングします(通常は継承によるが、時には構成による):
class Lion : HerbivoreEater { }
class Tiger : HerbivoreEater { }
abstract class HerbivoreEater : IHerbivoreEater
{
public void Eat(Herbivore herbivore) { ... }
}
...そしてすべてが順調です。ただし、機能がHerbivoreEater
クラスにあるため、各サブクラスでこれらの各動作のテストを行うことに問題があるように感じます。しかし、それは実際に消費されているサブクラスだし、それは彼らが重なって行動を共有するために(起こることのみ実装の詳細だLions
とTigers
、たとえば、全く異なる最終用途を有していてもよいです)。
同じコードを複数回テストすることは冗長に思われますが、サブクラスが基本クラスの機能をオーバーライドできる場合とオーバーライドする場合があります(そうです、それはLSPに違反する可能性がありますが、それに直面しようとすると、IHerbivoreEater
単に便利なテストインターフェイスです-それはエンドユーザーには関係ない場合があります)。したがって、これらのテストにはある程度の価値があると思います。
この状況で他の人は何をしますか?テストを基本クラスに移動するだけですか、または予想される動作についてすべてのサブクラスをテストしますか?
編集:
@pdrからの回答に基づいて、私はこれを考慮する必要があると思います。これIHerbivoreEater
は単なるメソッドシグネチャコントラクトです。動作は指定しません。例えば:
[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
ように、基本クラスに動作を配置すると、すべてのサブクラスが同じEat
動作を示すはずです。しかし、私はふたつの動作を共有している比較的無関係な2つのクラスについて話している。例えば、考えてみてFly
の行動をBrick
してPerson
いる、我々は、前提と同様の飛行挙動を示すが、それは必ずしも彼らが共通の基本クラスから派生持っている意味がないことがあります。
Animal
を含むクラスを持っていないのですEat
か?すべての動物は食べるので、Tiger
andLion
クラスは動物から継承できます。