在 Java 中测试使用另一个比较器的比较器的最佳方法是什么?

What's the best way to test a comparator that uses another comparator in Java?

从 TDD 的角度来看,我了解到当某些行为出现问题时,只有一个测试应该失败 - 其他失败通常具有误导性。如果这是真的并且我有两个比较器并且其中一个比较器使用另一个比较器,那么您如何测试它?我正在考虑为 "sub-comparator," 使用模拟,但是当例如使用 "parent comparator?"

对列表进行排序时,你将如何注入这个模拟

例如:

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();
    }
}

在上面,假设我已经测试了 "sub-comparators"(SomeSubComparator 和 SomeOtherSubComparator)。在这种情况下如何测试 SomeParentComparator 而没有 "true dependency" 在子比较器上(例如,模拟子比较器)?真的,这应该是一个 "workflow" 单元测试,只需确保 "sub-comparators" 被调用,对吧?怎么样?

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

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

然后你可以使用 setters 来设置你的 mocked subComaparators

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

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

  1. 构建你的单元测试 运行ner 以便依赖 class 的测试仅 运行 如果依赖 [=25] =]通过了。

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

在您提供的示例中,我看不出有任何理由说明您不能显式依赖。字段不需要是静态的 - 鉴于此 class 的所有对象都将以完全相同的方式运行。因此,最好有一个 'sub-comparators' 的显式集合,并期望调用者显式添加它们。

我认为您误解了 TDD 建议。

您实际上有两个可以独立测试的子比较器 class。然后你有 "parent" 比较器,它有子比较器的实例作为硬连线组件。单独测试它们......没有任何花哨的模拟。

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

从另一个角度考虑,@ShanuGupta 的回答建议您应该打破父比较器的抽象以允许模拟。大概您出于充分的理由封装了子比较器实例。现在您可以使用 DI 创建父构造函数......但是您再一次有效地打破了抽象,所以这个。

或者换一种方式。假设你打破了封装。现在你有一个父 class,(概念上)应该为 所有可能的 子比较器 class 工作。 (因为有人可以更改代码以使用不同的比较器而不更改父级本身。)但这可能意味着您要测试一组更复杂的行为。

在这种情况下,我不会为模拟而烦恼,而是将整个事物(所有 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 个测试用例,我不认为找到失败原因是一个真正的问题。