假设一个名为Sprinter
的类:
public class Sprinter {
protected int travelMeters;
public void run(int seconds) {
this.travelMeters = 9 * seconds;
}
public int getTravelMeters(){
return travelMeters;
}
}
继承SprintGenius
的{{1}}类型:
Sprinter
逻辑上,必须创建2个单元测试类,每种类型一个。
在class SprintGenius extends Sprinter {
public void run(int seconds) {
this.travelMeters = 10 * seconds;
}
}
单元测试中,我最终得到:
Sprinter
在@Before
public void setUp() {
Sprinter sprinter = new Sprinter();
}
public void testSprinterShouldRun90metersWithin10Seconds() {
sprinter.run(10);
assertEquals(sprinter.getTraveledMeters(),90);
}
单元测试中,我最终得到:
SprintGenius
在上面的两个测试中,我都会在10秒内测试行进的仪表数量。
显然,这两项测试都是绿色的。
然而,违反Liskov替换原则怎么办?
实际上,任何客户端代码都应该期望任何短跑运动员在9秒内完全跑10米。
3个解决方案(前两个解决方案将向所有团队的开发人员发出规则,并且必须承认和保留,即使不是每个人都能很好地掌握Liskov的概念)
1)在@Before
public void setUp() {
Sprinter sprinter = new SprintGenius();
}
public void testSprinterShouldRun100metersWithin10Seconds() {
sprinter.run(10);
assertEquals(sprinter.getTraveledMeters(),100);
}
课程中,复制每个测试但这一次基于Sprinter
并期望90米。 =>什么应该失败,这正是我们想要的! =>防止违反Liskov原则。
2)在Sprinter sprinter = new SuperGenius()
类中,总是根据完全相同的期望,基于基类添加每个测试的类似“克隆”。
所以,如果你有2个不同的测试,我们最终会得到4个测试。 2将Sprinter声明为SprintGenius
,将Sprinter
声明为Sprinter
。
3)永远不要继承具体课程(我想这是你通过阅读这篇文章的第一反应:)),如果它适合,更喜欢作文!所以这个问题不会发生。
基于许多开发人员忽略Liskov原则并且经常试图从具体类继承而不是使用其他更好的方式(如组合或不同的继承层次结构)这一事实来防止Liskov替换原则违规的最佳做法是什么?< / p>
我不想因为开发人员从我的书面类继承(不告诉我......),将其注入异构SprintGenius
列表的共享巨大列表并面对我'而烦恼你好奇怪的行为!'和调试时间......
我当然不希望宣布我的所有具体课程'最终':)
答案 0 :(得分:4)
单元测试是关于特定模块的测试,不能也不应该用于比这更广泛的事情。遵守Liskov替代原则是系统范围中的一个更广泛的问题,而不是模块范围。而且,它不是在代码中进行测试的东西。这是一个纯粹的设计问题,没有与实现相关联。我不认为LSP可以通过自动工具强制执行。它应该在设计评审期间以及稍后的代码审查期间处理(应检查是否符合设计)。
答案 1 :(得分:3)
这并不违反Liskov Substitution原则的合规性。这是糟糕的设计。您的两个类在行为方面没有差异,但在数据方面没有差异。所以,你应该只有一个班级,客户应该期望任何短跑运动员按照与其速度成比例的距离运行。
因此,您应该添加speed属性并使具有特定行为的单个类。
之后,您可以考虑使用新行为创建 realy extended 类并考虑测试。
使用此速度参数,Liskov替换原则不应该针对其他类型的跑步者,即使它们以不同的速度运行。
你的问题是:我的班级延伸一个人没有通过考试,因为我已经将一个人的名字从“彼得”改为“罗伯特”。
这是这个问题的坏榜样。正确的例子我认为是,退出良好的做法来测试它,但它是极端防御性的方法。你可以更好地利用你的时间来创建测试。此外,该测试将在很短的时间内过时,为了确保旧的行为正常工作,很难为新的子类添加测试。