Dalam banyak kasus saya mungkin memiliki kelas yang sudah ada dengan beberapa perilaku:
class Lion
{
public void Eat(Herbivore herbivore) { ... }
}
... dan saya memiliki unit test ...
[TestMethod]
public void Lion_can_eat_herbivore()
{
var herbivore = buildHerbivoreForEating();
var test = BuildLionForTest();
test.Eat(herbivore);
Assert.IsEaten(herbivore);
}
Sekarang, apa yang terjadi adalah saya perlu membuat kelas Tiger dengan perilaku yang identik dengan singa:
class Tiger
{
public void Eat(Herbivore herbivore) { ... }
}
... dan karena saya ingin perilaku yang sama, saya perlu menjalankan tes yang sama, saya melakukan sesuatu seperti ini:
interface IHerbivoreEater
{
void Eat(Herbivore herbivore);
}
... dan saya memperbaiki pengujian saya:
[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);
}
... dan kemudian saya menambahkan tes lain untuk Tiger
kelas baru saya :
[TestMethod]
public void Tiger_can_eat_herbivore()
{
IHerbivoreEater_can_eat_herbivore(BuildTigerForTest);
}
... dan kemudian saya memperbaiki kelas saya Lion
dan Tiger
(biasanya dengan warisan, tetapi kadang-kadang dengan komposisi):
class Lion : HerbivoreEater { }
class Tiger : HerbivoreEater { }
abstract class HerbivoreEater : IHerbivoreEater
{
public void Eat(Herbivore herbivore) { ... }
}
... dan semuanya baik-baik saja. Namun, karena fungsionalitasnya sekarang ada di HerbivoreEater
kelas, sekarang rasanya ada yang salah dengan melakukan tes untuk masing-masing perilaku ini di setiap subkelas. Namun itu adalah subclass yang benar-benar dikonsumsi, dan itu hanya detail implementasi bahwa mereka berbagi perilaku yang tumpang tindih ( Lions
dan Tigers
mungkin memiliki penggunaan akhir yang sama sekali berbeda, misalnya).
Tampaknya berlebihan untuk menguji kode yang sama beberapa kali, tetapi ada kasus di mana subclass dapat dan tidak menimpa fungsionalitas kelas dasar (ya, itu mungkin melanggar LSP, tetapi mari kita hadapi itu, IHerbivoreEater
hanya antarmuka pengujian yang nyaman - itu mungkin tidak masalah bagi pengguna akhir). Jadi tes ini memang memiliki nilai, saya pikir.
Apa yang dilakukan orang lain dalam situasi ini? Apakah Anda hanya memindahkan tes Anda ke kelas dasar, atau apakah Anda menguji semua subclass untuk perilaku yang diharapkan?
EDIT :
Berdasarkan jawaban dari @ pdr saya pikir kita harus mempertimbangkan ini: IHerbivoreEater
itu hanya kontrak metode tanda tangan; itu tidak menentukan perilaku. Contohnya:
[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
perilaku di kelas dasar, maka semua subclass harus menunjukkan Eat
perilaku yang sama . Namun, saya berbicara tentang 2 kelas yang relatif tidak berhubungan yang terjadi untuk berbagi perilaku. Pertimbangkan, misalnya, Fly
perilaku Brick
dan Person
yang, dapat kita asumsikan, menunjukkan perilaku terbang yang serupa, tetapi tidak selalu masuk akal untuk membuat mereka berasal dari kelas dasar yang sama.
Animal
kelas yang berisiEat
? Semua hewan makan, dan karenanya kelasTiger
danLion
dapat mewarisi dari hewan.