2

假设我有这个课程:

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

  // Assume constructor here...
}

我有一个类似的测试:

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

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

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

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

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

理想情况下,我想说“告诉我这个列表是否包含这个学生,并且对于任何浮点/双字段,与 .0001 的容差进行比较”

任何使这些断言保持简单的建议都值得赞赏。

4

4 回答 4

6

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

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

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

Assert(students.contains(expectedStudent)

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

Assert(students.stream().anyMatch(s -> expectedStudent.matches(s)));

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

3) 不要doubleequals(expected, actual).

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

public static void assertEquals(double expected, double actual, double delta) 

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

或者赞成BigDecimal那个double/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(...),
          );

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

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

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

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

  • containsExactly()断言提取的值与Tuples.

于 2019-07-11T18:34:15.760 回答
4

的行为是根据元素List.contains()的方法定义的。equals()因此,如果您的Student.equals()方法比较 gpa 是否完全相等并且您无法更改它,那么List.contains()对于您的目的而言,这不是一个可行的方法。

并且可能Student.equals() 不应该使用容差比较,因为很难看出如何使该类的hashCode()方法与这种方法一致equals()

也许您可以做的是编写一个替代的equals类似方法,例如“ matches()”,其中包含您的模糊比较逻辑。然后,您可以使用以下内容测试符合您标准的学生的列表

Assert(students.stream().anyMatch(s -> expectedStudent.matches(s)));

其中有一个隐式迭代,但List.contains().

于 2019-07-11T18:11:45.677 回答
1

如果您想使用containsor equals,那么您需要在 of 的equals方法中处理舍入Student

但是,我建议使用适当的断言库,例如 AssertJ。

于 2019-07-11T18:04:47.860 回答
1

我对 GPA 的概念不是特别熟悉,但我想它从未使用过超过 2 位小数的精度。3.8899999999994 GPA 根本没有多大意义,或者至少没有意义。

您实际上面临着人们在存储货币价值时经常面临的同样问题。3.89 英镑是有道理的,但 3.88999999 英镑没有。已经有大量信息可以处理这个问题。例如,请参阅这篇文章

TL;DR:我会将数字存储为整数。因此 3.88 GPA 将存储为 388。当您需要打印该值时,只需除以100.0。整数不存在与浮点值相同的精度问题,因此您的对象自然会更容易比较。

于 2019-07-11T18:14:17.937 回答