如何在 JUnit 中比较两个双精度列表
How to compare two lists of double in JUnit
在 JUnit 4 测试中,我有一个方法 getValues()
returns 我想将一个 List<Double>
对象与引用列表进行比较。到目前为止,我找到的最佳解决方案是像这样使用 org.hamcrest.collection.IsArray.hasItems
和 org.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 Matcher
的 List
,它不会根据 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);
}
}
在 JUnit 4 测试中,我有一个方法 getValues()
returns 我想将一个 List<Double>
对象与引用列表进行比较。到目前为止,我找到的最佳解决方案是像这样使用 org.hamcrest.collection.IsArray.hasItems
和 org.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 Matcher
的 List
,它不会根据 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);
}
}