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

标签 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 类型继承 Sprinter:

class SprintGenius extends Sprinter {

    public void run(int seconds) {
        this.travelMeters = 10 * seconds;
    }
}

逻辑上,必须创建 2 个单元测试类,每种类型一个。

Sprinter 单元测试中,我最终会得到:

@Before
public void setUp() {
  Sprinter sprinter = new Sprinter();
}

public void testSprinterShouldRun90metersWithin10Seconds() {
  sprinter.run(10);
  assertEquals(sprinter.getTraveledMeters(),90);
}

SprintGenius 单元测试中,我最终会得到:

@Before
public void setUp() {
  Sprinter sprinter = new SprintGenius();
}

public void testSprinterShouldRun100metersWithin10Seconds() {
  sprinter.run(10);
  assertEquals(sprinter.getTraveledMeters(),100);
}

在上面的两个测试中,我都会在 10 秒内测试行进的米数。

显然,这两个测试都是绿色的。

但是,违反里氏替换原则怎么办?

事实上,任何客户端代码都应该期望任何短跑运动员在 9 秒内跑完 10 米。

3 个解决方案(前两个解决方案已向所有团队的开发人员发出规则,并且必须承认并保留,即使不是每个人都很好地掌握 Liskov 的概念)

1) 在 Sprinter 类中,重复每个测试,但这次基于 Sprinter sprinter = new SuperGenius() 并期望 90 米。 => 什么应该失败,这正是我们想要的! => 防止违反 Liskov 原则。

2) 在SprintGenius 类中,始终基于完全相同的期望添加基于基类的每个测试的类似“克隆”。 因此,如果您有 2 个不同的测试,我们最终会得到 4 个测试。 2 将 Sprinter 声明为 Sprinter 并 2 将 Sprinter 声明为 SprintGenius

3) 从不从具体类继承(我想这是你阅读这篇文章的第一 react :)),如果合适的话,更喜欢组合!这样就不会出现这个问题。

基于许多开发人员忽略 Liskov 原则并且经常试图从具体类继承而不是使用另一种更好的方法(如组合或不同的继承层次结构)这一事实,防止违反 Liskov 替换原则的最佳实践是什么?

我不想因为开发人员继承了我编写的类(没有告诉我......),将它注入(inject)到一个共享的巨大的异构 Sprinter 列表中并面对我说“你好奇怪的行为!”和数小时的调试时间...

我当然不想将我所有的具体类都声明为“final”:)

最佳答案

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

关于java - 对 Liskov 替换原则的合规性进行单元测试是一种好的做法吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13194154/

相关文章:

inheritance - 可继承的对象结构

java - 如何使用 Javascript 或 Java Servlet 获取客户端信息?

java - 如何将 double 四舍五入到小数点后一位?

java - 如何在 Jetty 9.2.24 HttpClient 中添加线程池?

java - Paypal 与 java 集成

python - 不同目录中的测试调用相同时测试发现失败

java - 生成一个充满任意值的模拟对象

java - 关于@ForceDiscriminator/@DiscriminatorOptions(force=true)的使用

Django 单元测试 : South-migrated DB works in MySQL, 在 PostGreSQL 中抛出重复的 PK 错误。我错过了什么还是这是一个错误?

c++ - 没有成员函数的结构的继承