Java List.contains对象具有双精度公差
假设我有一门课:Java List.contains对象具有双精度公差,java,unit-testing,collections,floating-point,floating-point-comparison,Java,Unit Testing,Collections,Floating Point,Floating Point Comparison,假设我有一门课: 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(studen
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)
List students=getStudents();
学生期望学生=新生(1234,“彼得·史密斯”,3.89)
断言(students.contains)(expectedStudent)
现在,如果getStudents()方法将Peter的GPA计算为3.8899999994,那么该测试将失败,因为3.8899999994!=3.89
我知道我可以用对单个双精度/浮点值的容差来做一个断言,但是有没有一种简单的方法可以用“contains”来做这项工作,这样我就不必单独比较学生的每个字段(我将编写许多类似的测试,我将测试的实际类将包含更多的字段)
我还需要避免修改相关类(即Student)以添加自定义相等逻辑
另外,在我的实际类中,将有其他双值的嵌套列表,需要使用容差进行测试,如果我必须单独断言每个字段,这将使断言逻辑更加复杂
理想情况下,我想说“告诉我此列表是否包含此学生,对于任何浮点/双精度字段,请使用.0001的公差进行比较”
如果您想使用
包含或等于
,那么您需要注意学生
的等于
方法的四舍五入
但是,我建议使用适当的断言库,如AssertJ。列表.contains()
的行为是根据元素的equals()
方法定义的。因此,如果您的Student.equals()
方法比较GPA是否精确相等,并且您无法更改它,则List.contains()
对于您的目的来说不是一种可行的方法
可能Student.equals()
不应该使用与容差的比较,因为很难看出如何使该类的hashCode()
方法与这样的equals()
方法一致
也许你能做的是编写一个替代方法,equals
-like方法,比如“matches()
”,它包含你的模糊比较逻辑。然后你可以测试一个学生的列表,该学生符合你的标准,比如
Assert(students.stream().anyMatch(s -> expectedStudent.matches(s)));
这里面有一个隐式的迭代,但是对于List.contains()
我对GPA的概念不是特别熟悉,但我可以想象它从来不会超过小数点后2位。3.88999999994 GPA根本没有多大意义,或者至少没有意义
实际上,您正面临着人们在存储货币价值时经常面临的相同问题。3.89英镑是有意义的,但3.889999英镑不是。已经有大量信息可用于处理此问题。例如,请参阅
TL;DR:我会将数字存储为整数。因此3.88 GPA将存储为388。当您需要打印值时,只需除以100.0
。整数与浮点值没有相同的精度问题,因此您的对象自然更易于比较。1)不要仅出于单元测试目的重写equals/hashCode
Assert(students.contains(expectedStudent)
这些方法具有语义,并且它们的语义没有考虑类的所有字段,从而使测试断言成为可能
2)依靠测试库来执行断言
Assert(students.contains(expectedStudent)
或者(发表在约翰·博林格的回答中):
在单元测试方面是非常好的反模式。
当断言失败时,您需要做的第一件事是知道错误的原因以更正测试。
依靠布尔值来断言列表比较根本不允许这样做。
KISS(保持简单和愚蠢):使用测试工具/功能进行断言,不要重新发明轮子,因为它们将在测试失败时提供所需的反馈。
3)不要断言double
与相等(预期、实际)
Assert(students.contains(expectedStudent)
要断言双值,单元测试库在断言中提供第三个参数,以指定允许的增量,例如:
public static void assertEquals(double expected, double actual, double delta)
在JUnit5中(JUnit4也有类似的功能)
或者选择更适合这种比较的BigDecimal
到double/float
但它不能完全解决您的需求,因为您需要断言实际对象的多个字段。使用循环来实现这一点显然不是一个好的解决方案。
Matcher库提供了一种有意义且优雅的方法来解决这个问题
4)使用Matcher库对实际列表对象的特定属性执行断言
使用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(...),
);
//给定
...
//什么时候
List students=getStudents();
//然后
断言。断言(学生)
//双精度值的允许差值为0.1
.使用ComparatorForType(新的DoubleComparator(0.1),Double.class)
.Extraction(Student::getId、Student::getName、Student::getGpa)
.containsExactly(元组(1234,“彼得·史密斯”,3.89),
元组(…),
);
一些解释(所有这些都是AssertJ功能):
使用ComparatorForType()
允许为给定类型的元素或其字段设置特定的比较器
DoubleComparator
是一个AssertJ比较器,提供在双重比较中考虑ε的便利
提取
定义要从列表中包含的实例中断言的值
containsExactly()
断言提取的值与元组中定义的值完全相同(即不多、不少且顺序准确)
没有