【问题标题】:How to compare two lists of double in JUnit如何在 JUnit 中比较两个双精度列表
【发布时间】:2016-03-15 15:15:09
【问题描述】:

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

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

这适用于只返回少量值的测试。但是如果一个测试返回更多的值,这绝对不是最好的方法。

我还尝试了以下代码。编译代码需要在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: 预期:(包含 的 内的数值, 的 内的数值]> 的集合) 得到:

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

【问题讨论】:

  • 有一个总是有效的解决方案:我遍历 getValues() 的值并与我的参考值进行一对一的比较。我想知道是否有使用 Matchers 的更简单的解决方案。
  • 测试驱动开发 - 实施由您的测试驱动。如果某些东西难以测试,那么您可能做得不对。您不能为双打列表编写测试 - 不要使用双打。为什么不使用整数/长整数或BigDecimals
  • @Jaroslav 此测试发生在执行密集数值计算的应用程序中。所以不使用双打列表是不可能的。
  • @Francois 您正在尝试解决问题,而不是解决问题的原因。这些双标代表什么?为什么精度很重要?为什么你的 epsilon 是 0.0001 而不是 0.000001 或 0.1?
  • @Jaroslav 在我给出的示例中,这些值是 0.1 和 0.4 之间线性插值的结果,增量为 0.1。我希望测试返回一个包含 0.2 和 0.3 作为缺失值的列表。这是我必须测试的最简单的情况之一。此测试失败,因为列表包含 0.2 和 0.300000004,除非我使用适当的匹配器。

标签: java junit hamcrest


【解决方案1】:

我认为这里的正确解决方案是自定义Matcher。基本上像IsIterableContainingInOrder 这样的东西只适用于双打并支持误差范围。

【讨论】:

    【解决方案2】:

    如果您愿意从List&lt;Double&gt; 转换为double[]assertArrayEquals 允许指定容错:

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

    Java 8 中,从列表到数组的转换比较干净(参见related question)。例如:

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

    那么断言语句可以如下:

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

    附言 只需将签名更改为double[] toArray(List&lt;? extends Number&gt; list),即可使toArray 与任何Number 类型一起使用。

    【讨论】:

    • 这不处理空值,这在双精度列表中是可能的。
    【解决方案3】:

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

    hasItems() 的返回值转换为Matcher 隐藏了一个类型错误。您的代码实际上是在检查 getValues() 的结果是否是具有您创建的 2 个 Matchers 的 List,它不会根据 getValues() 的结果评估那些 Matchers。

    这是因为hasItems() 没有像contains() 那样采用List&lt;Matcher&lt;? super T&gt;&gt; 的重载。

    这可以满足您的需求,而无需经历自定义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&lt;Matcher&lt;Double&gt;&gt;,它会选择错误的重载 contains()

    【讨论】:

      【解决方案4】:

      如果您愿意将 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);
        }
      }
      

      【讨论】:

        【解决方案5】:

        没有将匹配器列表作为参数的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)));
        

        【讨论】:

          【解决方案6】:

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

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

            assertEquals(expected,actual);

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

          【讨论】:

          • 人们确实提到了它,被否决并删除了他们的答案,因为他们离解决 OP 的问题还差得很远。
          • @JaroslawPawlak 但是为什么呢?
          • 出于同样的原因4.35d * 100 != 435.0d
          • 对不起,我只是想理解这个概念,所以如果我在以下列表中调用 equals Arrays.asList((double) 430, (double) 2)Arrays.asList(4.3 * 100, (double) 2),它应该是假的吗?
          • @RahulSharma 问题在于这两个列表不同但数值相等。在被删除的帖子中,我提到过 0.2 和 0.3 的列表不等于 0.2 和 0.300000004 的列表。使这两个列表相等的唯一方法(这就是这篇文章的内容)是在比较中引入一个数值容差。你的解决方案没有解决我的问题。
          【解决方案7】:

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

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

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

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

          【讨论】:

          • 排序列表?什么?如果订单很重要怎么办?如果预期是[3.0, 1.0, 2.0] 但它返回[2.0, 1.0, 3.0] 怎么办?您的测试将通过,即使它不应该通过。
          • 好吧,如果顺序很重要,您不必对列表进行排序。
          • 我显然误解了这个问题。对不起,太累了。另外,如果方法的名称是 hasItems,顺序会有什么影响?默认情况下,这意味着顺序无关紧要,所以我不太明白您对此的评论。真正的问题是我的回答是如何比较两个列表,而不是如果一个列表包含另一个列表的元素。但是,使用相同的方法仍然可以在 O(N log N) 中解决。我不知道 hamcrest 是如何工作的,但我知道问题在于复杂性?
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-03-11
          • 2022-10-01
          • 1970-01-01
          • 2015-12-17
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多