【问题标题】:What's the best way to test a comparator that uses another comparator in Java?测试在 Java 中使用另一个比较器的比较器的最佳方法是什么?
【发布时间】:2018-04-27 03:39:53
【问题描述】:

从 TDD 的角度来看,我了解到,当某些行为出现问题时,只有一个测试会失败 - 其他失败通常会产生误导。如果这是真的,我有两个比较器,其中一个比较器使用另一个比较器,你如何测试这个?我正在考虑为“子比较器”使用模拟,但是当例如使用“父比较器”对列表进行排序时,你将如何注入这个模拟?

例如:

public class SomeParentComparator implements Comparator<SomeType> {

    private static final SomeSubComparator subComparator1 = new SubComparator();
    private static final SomeOtherSubComparator subComparator2 = new SomeOtherSubComparator();

    @Override
    public int compare(SomeType someType1, SomeType someType2) {
        return new CompareToBuilder()
                .append(someType1.foo, someType2.foo, subComparator1)
                .append(someType1.bar, someType2.bar, subComparator2)
                .toComparison();
    }
}

在上面,假设我已经测试了“子比较器”(SomeSubComparator 和 SomeOtherSubComparator)。在这种情况下如何测试 SomeParentComparator 而不对子比较器有“真正的依赖”(例如,模拟子比较器)?真的,这应该以某种方式成为“工作流程”单元测试,并确保调用“子比较器”,对吗?怎么样?

【问题讨论】:

  • 你能展示一个代码示例吗?
  • 刚刚添加。我很快就编好了,所以希望不会有太多的 type-o :)
  • 您可以创建一个模拟数据,其中子比较器比较无关紧要。
  • @ShanuGupta 不知道你的意思 - 以及如何?此外,如果这应该是一个答案,它应该写成一个答案,而不是一个答复。它只是在这里增加了噪音。

标签: java unit-testing tdd comparator


【解决方案1】:

您的SomeParentComparator 很难独立测试,因为您直接在类对象的实例变量中初始化subComparator1subComparator2

我建议为这两个字段设置 setter 和 getter,并使用 setter 或构造函数初始化它们。

然后您可以使用setters 来设置您的模拟subComaparators

您还可以创建一个模拟数据,其中 subComparator 比较无关紧要。我可以打个比方,比如你想用他们的名字和姓氏对People 对象进行排序。您的父比较器按名字排序,子比较器按姓氏排序。那么你的模拟数据将是一个People 的列表,其姓氏都相同。

【讨论】:

    【解决方案2】:

    理想情况下,您的所有类都将注入所有依赖项而不是隐式依赖项(在您的情况下,通过私有静态字段)。但是,在很多情况下,删除所有隐式依赖项会使您的代码过于复杂。在这种情况下,您有两个单元测试选项:

    1. 构建单元测试运行程序,以便依赖类的测试仅在依赖类的测试通过时运行。

    2. 在单元测试期间使用类似 Powermock 的东西绕过封装并注入模拟依赖项。这将允许依赖类的测试通过,即使依赖的类被破坏。

    在你给出的例子中,我看不出你不能明确依赖的任何理由。字段不需要是静态的——因为这个类的所有对象都会以完全相同的方式表现。因此,最好有一个明确的“子比较器”集合,并期望调用者明确地添加它们。

    【讨论】:

    • 恕我直言,即使 powermock 在这种情况下也无法模拟子比较器,不是吗?
    • @ShanuGupta 我相信 Whitebox.setInternalState 可以设置私有静态变量的值。
    【解决方案3】:

    我认为您误解了 TDD 建议。

    您实际上有两个可以独立测试的子比较器类。然后你有一个“父”比较器,它具有子比较器的实例作为硬连线组件。只需单独测试它们......没有任何花哨的嘲笑。

    当然,父比较器的正确性取决于子比较器的正确性。但是由于前者和后者是不可分割的,因此将父级视为测试目的的黑匣子更容易也更正确。

    以另一种方式考虑,@ShanuGupta 的回答建议您应该打开父比较器的抽象以允许模拟。大概您出于充分的理由封装了 subcomarator 实例。现在您可以使用 DI 创建父构造函数……但您又一次有效地打破了抽象,因此它。

    或者其他方式。假设您打开封装。现在你有一个父类(在概念上)应该适用于所有可能的子比较器类。 (因为有人可以更改代码以使用不同的比较器而不更改父对象本身。)但这可能意味着您需要测试一组更复杂的行为。

    【讨论】:

      【解决方案4】:

      在这种情况下,我不会为模拟而烦恼,而是将整个事物(所有 3 个比较器)作为一个单元进行测试。

      我想测试可能如下所示:

      @Test
      public void parent_with_smaller_foo_and_equal_bar_is_smaller() {
        var parentA = aParent().withFoo("A").withBar("C");
        var parentB = aParent().withFoo("B").withBar("C");
      
        assertThat(parentA).isLessThan(parentB);
      }
      
      @Test
      public void parent_with_equal_foo_and_equal_bar_is_equal() {
        var parentA = aParent().withFoo("A").withBar("C");
        var parentB = aParent().withFoo("A").withBar("C");
      
        assertThat(parentA).isEqualByComparingTo(parentB);
      }
      

      等等。如果您将以上内容写为:

      (A,C)<(B,C)
      (A,C)=(A,C)
      

      那么看起来我们至少还需要 3 个案例来测试所有三个比较器:

      (B,C)>(A,C)
      (A,B)<(A,C)
      (A,C)>(A,B)
      

      我会将SubComparatorSomeOtherSubComparator 保留为包私有类,并将它们视为SomeParentComparator 的实现细节。

      只有 5 个测试用例,我不认为找到失败的原因是一个真正的问题。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-08-21
        相关资源
        最近更新 更多