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 Tigerkelas baru saya :
[TestMethod]
public void Tiger_can_eat_herbivore()
{
IHerbivoreEater_can_eat_herbivore(BuildTigerForTest);
}
... dan kemudian saya memperbaiki kelas saya Liondan 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 HerbivoreEaterkelas, 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 ( Lionsdan Tigersmungkin 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, IHerbivoreEaterhanya 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: IHerbivoreEateritu 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);
}
Eatperilaku di kelas dasar, maka semua subclass harus menunjukkan Eatperilaku yang sama . Namun, saya berbicara tentang 2 kelas yang relatif tidak berhubungan yang terjadi untuk berbagi perilaku. Pertimbangkan, misalnya, Flyperilaku Brickdan Personyang, dapat kita asumsikan, menunjukkan perilaku terbang yang serupa, tetapi tidak selalu masuk akal untuk membuat mereka berasal dari kelas dasar yang sama.
Animalkelas yang berisiEat? Semua hewan makan, dan karenanya kelasTigerdanLiondapat mewarisi dari hewan.