如何在 JUnit 中比较两个双精度列表

How to compare two lists of double in JUnit

在 JUnit 4 测试中,我有一个方法 getValues() returns 我想将一个 List<Double> 对象与引用列表进行比较。到目前为止,我找到的最佳解决方案是像这样使用 org.hamcrest.collection.IsArray.hasItemsorg.hamcrest.Matchers.closeTo

assertThat(getValues(), hasItems(closeTo(0.2, EPSILON), closeTo(0.3, EPSILON)));

这对于 returns 只有几个值的测试很顺利。但是如果测试returns更多的值,这绝对不是最好的方法。

我也试过下面的代码。编译代码需要在 hasItems 之前向下转换为 Matcher

List<Matcher<Double>> doubleMatcherList = new ArrayList<Matcher<Double>>();
doubleMatcherList.add(closeTo(0.2, EPSILON));
doubleMatcherList.add(closeTo(0.3, EPSILON));
assertThat(getValues(), (Matcher) hasItems(doubleMatcherList));

比较失败,我不明白为什么:

java.lang.AssertionError: Expected: (a collection containing <[a numeric value within <1.0E-6> of <0.2>, a numeric value within <1.0E-6> of <0.3>]>) got: <[0.2, 0.30000000000000004]>

有没有更好的方法来比较两个大的双打列表?这里的难点是需要一个数值公差来验证 getValues() 的结果是否等于我的参考列表。这种比较对于任何对象列表来说似乎都很容易,但对于 Double.

列表则不然

如果两个列表的长度不同则说它们不同。如果它们的长度相同,则对列表进行排序。对它们进行排序后,比较相同索引处的元素。

这可以更详细地讨论是否应该将列表 A 中的每个元素与列表 B 中的相应元素进行比较,例如比较 A[i] 与 B[i-d], ... B[i], ..., B[i + d].

但对于初学者来说,这可能会帮助您获得更好的想法。

更新:如果您不能修改列表,您可以随时复制它们并对其进行排序。

我认为这里正确的解决方案是自定义 Matcher. Basically something like IsIterableContainingInOrder,它只适用于双打并支持误差范围。

我相信没有人提到它。如果您有预期的列表,并且

  • 希望实际列表与预期列表的顺序相同,然后使用

    assertEquals(expected,actual);

  • 希望列表无论顺序如何都相等,然后执行 assertTrue(expected.containsAll(actual) && actual.containsAll(expected));

没有 hasItems 方法将匹配器列表作为参数。但是你可以使用 the one with varargs.

首先将匹配器列表转换为数组

@SuppressWarnings("unchecked")
private Matcher<Double>[] toDoubleMatcherArray(List<Matcher<Double>> doubleMatchers) {
    return doubleMatchers.toArray(new Matcher[0]);
}

然后这样称呼它

assertThat(getValues(), hasItems(toDoubleMatcherArray(doubleMatcherList)));

如果您愿意从 List<Double> 转换为 double[]assertArrayEquals 允许指定错误容忍度:

assertArrayEquals(new double[] {1, 2}, new double[] {1.01, 2.09}, 1E-1);

Java 8, the conversion from list to array is relatively clean (see related question)。例如:

double[] toArray(List<Double> list) {
    return list.stream().mapToDouble(Number::doubleValue).toArray();
}

断言语句可以如下所示:

assertArrayEquals(toArray(refList), toArray(getValues()), 1E-9);

p.s。 toArray 可以与任何 Number 类型一起工作,只需将签名更改为 double[] toArray(List<? extends Number> list).

您需要使用 contains() 而不是 hasItems()

hasItems() 的 return 转换为 Matcher 隐藏了类型错误。您的代码实际上是在检查 getValues() 的结果是否是具有您创建的 2 MatcherList,它不会根据 Matcher 的结果评估这些 Matcher getValues().

这是因为 hasItems() 没有像 contains() 那样使用 List<Matcher<? super T>> 的重载。

这可以满足您的需求,无需经历自定义的麻烦 Matcher:

List<Matcher<? super Double>> doubleMatcherList = new ArrayList<>();
doubleMatcherList.add(closeTo(0.2, EPSILON));
doubleMatcherList.add(closeTo(0.3, EPSILON));
assertThat(getValues(), contains(doubleMatcherList));

注意 doubleMatcherList 的参数化类型。如果它只是 List<Matcher<Double>> 它选择了 contains().

的错误重载

如果您愿意从 Hamcrest 更改为 AssertJ 断言,这里是 java 8.

中的解决方案
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import org.junit.Test;
// import java.util.function.Function;

public class DoubleComparatorTest {

  // private static final Double DELTA = 1E-4;
  // private static final Function<Double, Comparator<Double>> DOUBLE_COMPARATOR_WITH_DELTA =
  // (delta) -> (o1, o2) -> (o1 - o2 < delta) ? 0 : o1.compareTo(o2);

  private Comparator<Double> COMPARATOR = (o1, o2) -> (o1 - o2 < 0.0001) ? 0 : o1.compareTo(o2);

  @Test
  public void testScaleCalculationMaxFirstPositive() {
    List<Double> expected = Arrays.asList(1.1D, 2.2D, 3.3D);
    List<Double> actual = Arrays.asList(1.10001D, 2.2D, 3.30001D);

    assertThat(actual)
        // .usingElementComparator(DOUBLE_COMPARATOR_WITH_DELTA.apply(DELTA))
        .usingElementComparator(COMPARATOR)
        .isEqualTo(expected);
  }
}