Java List.contains 具有双精度的对象

Joe*_*jr2 2 java floating-point collections unit-testing floating-point-comparison

假设我有这门课:

public class Student
{
  long studentId;
  String name;
  double gpa;

  // Assume constructor here...
}
Run Code Online (Sandbox Code Playgroud)

我有一个类似的测试:

List<Student> students = getStudents();
Student expectedStudent = new Student(1234, "Peter Smith", 3.89)
Assert(students.contains(expectedStudent)
Run Code Online (Sandbox Code Playgroud)

现在,如果 getStudents() 方法将 Peter 的 GPA 计算为类似于 3.8899999999994,那么该测试将失败,因为 3.8899999999994 != 3.89。

我知道我可以对单个双精度/浮点值做一个容忍度的断言,但是有没有一种简单的方法可以使这个与“包含”一起工作,这样我就不必单独比较 Student 的每个字段(我将编写许多类似的测试,而我将要测试的实际类将包含更多字段)。

我还需要避免修改有问题的类(即学生)以添加自定义相等逻辑。

此外,在我的实际课程中,将有其他 double 值的嵌套列表需要进行容差测试,如果我必须单独断言每个字段,这将使断言逻辑更加复杂。

理想情况下,我想说“请告诉我此列表是否包含该学生,对于任何浮点/双精度字段,请以 .0001 的容差进行比较”

感谢任何保持这些断言简单的建议。

dav*_*xxx 5

1) 不要仅出于单元测试目的而覆盖 equals/hashCode

这些方法具有语义,并且它们的语义并未考虑类的所有字段以使测试断言成为可能。

2)依靠测试库来执行你的断言

Assert(students.contains(expectedStudent)
Run Code Online (Sandbox Code Playgroud)

或者那个(发布在 John Bollinger 的回答中):

Assert(students.stream().anyMatch(s -> expectedStudent.matches(s)));
Run Code Online (Sandbox Code Playgroud)

在单元测试方面是很好的反模式。
当断言失败时,您首先需要知道错误的原因以纠正测试。
依靠布尔值来断言列表比较根本不允许这样做。
KISS(保持简单和愚蠢):使用测试工具/功能来断言并且不要重新发明轮子,因为它们会在您的测试失败时提供所需的反馈。

3)不要断言doubleequals(expected, actual)

为了断言双精度值,单元测试库在断言中提供了第三个参数来指定允许的增量,例如:

public static void assertEquals(double expected, double actual, double delta) 
Run Code Online (Sandbox Code Playgroud)

在 JUnit 5 中(JUnit 4 有类似的东西)。

或有利于BigDecimaldouble/float更适合于这种比较。

但它不会完全解决您的要求,因为您需要断言实际对象的多个字段。使用循环来做到这一点显然不是一个好的解决方案。
Matcher 库提供了一种有意义且优雅的方式来解决这个问题。

4) 使用 Matcher 库对实际 List 的对象的特定属性进行断言

使用 AssertJ :

//GIVEN
...

//WHEN
List<Student> students = getStudents();

//THEN
Assertions.assertThat(students)
           // 0.1 allowed delta for the double value
          .usingComparatorForType(new DoubleComparator(0.1), Double.class) 
          .extracting(Student::getId, Student::getName, Student::getGpa)
          .containsExactly(tuple(1234, "Peter Smith", 3.89),
                           tuple(...),
          );
Run Code Online (Sandbox Code Playgroud)

一些解释(所有这些都是 AssertJ 特性):

  • usingComparatorForType() 允许为给定类型的元素或其字段设置特定的比较器。

  • DoubleComparator 是一个 AssertJ 比较器,提供了在双重比较中考虑 epsilon 的便利。

  • extracting 定义要从 List 中包含的实例断言的值。

  • containsExactly()断言提取的值恰好是(不多也不少,并且按照确切的顺序)在Tuples 中定义的这些值。