Trong rất nhiều trường hợp tôi có thể có một lớp hiện có với một số hành vi:
class Lion
{
public void Eat(Herbivore herbivore) { ... }
}
... và tôi có một bài kiểm tra đơn vị ...
[TestMethod]
public void Lion_can_eat_herbivore()
{
var herbivore = buildHerbivoreForEating();
var test = BuildLionForTest();
test.Eat(herbivore);
Assert.IsEaten(herbivore);
}
Bây giờ, những gì xảy ra là tôi cần tạo ra một lớp Tiger với hành vi hợp lý với Sư tử:
class Tiger
{
public void Eat(Herbivore herbivore) { ... }
}
... Và vì tôi muốn có hành vi tương tự, tôi cần chạy thử nghiệm tương tự, tôi làm một cái gì đó như thế này:
interface IHerbivoreEater
{
void Eat(Herbivore herbivore);
}
... Và tôi cấu trúc lại bài kiểm tra của mình:
[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);
}
... và sau đó tôi thêm một bài kiểm tra khác cho Tiger
lớp mới của mình :
[TestMethod]
public void Tiger_can_eat_herbivore()
{
IHerbivoreEater_can_eat_herbivore(BuildTigerForTest);
}
... và sau đó tôi cấu trúc lại các lớp của tôi Lion
và Tiger
(thường là do thừa kế, nhưng đôi khi theo thành phần):
class Lion : HerbivoreEater { }
class Tiger : HerbivoreEater { }
abstract class HerbivoreEater : IHerbivoreEater
{
public void Eat(Herbivore herbivore) { ... }
}
... Và tất cả đều tốt. Tuy nhiên, vì chức năng hiện có trong HerbivoreEater
lớp, nên bây giờ có cảm giác có gì đó không ổn khi có các bài kiểm tra cho từng hành vi này trên mỗi lớp con. Tuy nhiên, đó là các lớp con thực sự đang được tiêu thụ và đó chỉ là một chi tiết triển khai mà chúng xảy ra để chia sẻ các hành vi chồng chéo ( ví dụ Lions
, và Tigers
có thể có các mục đích sử dụng hoàn toàn khác nhau).
Có vẻ không cần thiết để kiểm tra cùng một mã nhiều lần, nhưng có những trường hợp lớp con có thể và ghi đè chức năng của lớp cơ sở (vâng, nó có thể vi phạm LSP, nhưng hãy đối mặt với nó, IHerbivoreEater
chỉ là giao diện kiểm tra thuận tiện - nó có thể không quan trọng đối với người dùng cuối). Vì vậy, những thử nghiệm này có một số giá trị, tôi nghĩ.
Những người khác làm gì trong tình huống này? Bạn chỉ di chuyển bài kiểm tra của mình đến lớp cơ sở hay bạn kiểm tra tất cả các lớp con cho hành vi dự kiến?
CHỈNH SỬA :
Dựa trên câu trả lời từ @pdr tôi nghĩ chúng ta nên xem xét điều này: đây IHerbivoreEater
chỉ là một hợp đồng chữ ký phương thức; nó không chỉ định hành vi. Ví dụ:
[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
hành vi trong lớp cơ sở, thì tất cả các lớp con sẽ thể hiện Eat
hành vi tương tự . Tuy nhiên, tôi đang nói về 2 lớp tương đối không liên quan đến nhau để chia sẻ một hành vi. Ví dụ, xem xét Fly
hành vi của Brick
và Person
chúng ta có thể giả sử, thể hiện một hành vi bay tương tự, nhưng không nhất thiết phải có chúng xuất phát từ một lớp cơ sở chung.
Animal
lớp có chứaEat
? Tất cả động vật ăn, và do đóTiger
,Lion
lớp và có thể thừa hưởng từ động vật.