对Liskov Substitution原则的合规性进行单元测试是一种好的做法吗?

时间:2012-11-02 11:17:00

标签: java unit-testing inheritance liskov-substitution-principle

假设一个名为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列表的共享巨大列表并面对我'而烦恼你好奇怪的行为!'和调试时间......

我当然不希望宣布我的所有具体课程'最终':)

2 个答案:

答案 0 :(得分:4)

单元测试是关于特定模块的测试,不能也不应该用于比这更广泛的事情。遵守Liskov替代原则是系统范围中的一个更广泛的问题,而不是模块范围。而且,它不是在代码中进行测试的东西。这是一个纯粹的设计问题,没有与实现相关联。我不认为LSP可以通过自动工具强制执行。它应该在设计评审期间以及稍后的代码审查期间处理(应检查是否符合设计)。

答案 1 :(得分:3)

这并不违反Liskov Substitution原则的合规性。这是糟糕的设计。您的两个类在行为方面没有差异,但在数据方面没有差异。所以,你应该只有一个班级,客户应该期望任何短跑运动员按照与其速度成比例的距离运行

因此,您应该添加speed属性并使具有特定行为的单个类。

之后,您可以考虑使用新行为创建 realy extended 类并考虑测试。

使用此速度参数,Liskov替换原则不应该针对其他类型的跑步者,即使它们以不同的速度运行。

你的问题是:我的班级延伸一个人没有通过考试,因为我已经将一个人的名字从“彼得”改为“罗伯特”。

这是这个问题的坏榜样。正确的例子我认为是,退出良好的做法来测试它,但它是极端防御性的方法。你可以更好地利用你的时间来创建测试。此外,该测试将在很短的时间内过时,为了确保旧的行为正常工作,很难为新的子类添加测试。