在Java中使用toString()进行单元测试
在单元测试中,根据toString()返回的字符串测试返回值通常是一个好主意吗 例如,执行以下操作以确保返回预期列表:在Java中使用toString()进行单元测试,java,unit-testing,tostring,Java,Unit Testing,Tostring,在单元测试中,根据toString()返回的字符串测试返回值通常是一个好主意吗 例如,执行以下操作以确保返回预期列表: assertEquals(someExpression.toString() ,"[a, b, c]"); 在我看来,考虑因素如下: 优点:节省时间(构建实际期望值需要更长的代码) 缺点:该测试依赖于文档中未正式定义的toString(),因此在未来的任何版本中都可能发生更改。最好覆盖等于和哈希代码(自行或使用Lombok),并使用它们进行比较,而不是使用toString。
assertEquals(someExpression.toString() ,"[a, b, c]");
在我看来,考虑因素如下:
优点:节省时间(构建实际期望值需要更长的代码)
缺点:该测试依赖于文档中未正式定义的toString(),因此在未来的任何版本中都可能发生更改。最好覆盖
等于和哈希代码(自行或使用Lombok),并使用它们进行比较,而不是使用toString
。通过这种方式,您可以明确地对等式进行测试,然后可以重用这些方法
然后您可以执行以下操作(以列表为例):
Assert.assertThat()
方法具有特定于集合的匹配器以及更多
例如,contains()
适用于集合:
List<String> someExpression = asList("a", "b", "c");
assertThat(someExpression.toString(), contains("a", "b", "c"));
我对对象的toString()
进行测试的唯一一次是当我有一个不可编辑的类时,该类没有实现hashcode
或equals
,而是实现了toString()
来输出其字段的内容。即使这样,我也不会使用硬编码字符串作为相等性测试,而是执行以下操作
SomeObject b = new SomeObject(expected, values, here);
assertEquals(a.toString(), b.toString());
您的方法最初可能会节省时间,但从长远来看,维护测试需要花费更多的时间,因为您正在对toString()的预期结果字符串进行硬编码
编辑1:当然,如果您正在测试输出字符串的函数/进程,那么您应该使用硬编码字符串作为预期结果
String input = "abcde";
String result = removeVowels(input);
assertEquals(result, "bcd");
我在特定情况下使用toString
,特别是当等效代码要复杂得多时。当您得到更复杂的数据结构时,您需要一种快速的方法来测试整个结构。如果您编写代码进行测试,一个字段一个字段地进行测试,那么您可能会忘记添加字段
我举个例子
这个测试失败了,为什么?您可以很容易地在IDE中看到它产生的结果
当您得到更复杂的示例时,检查每个(嵌套的)值并在以后它中断时更正它是非常乏味的。一个较长的例子是
assertEquals("--- !!meta-data #binary\n" +
"header: !SCQStore {\n" +
" wireType: !WireType BINARY,\n" +
" writePosition: 0,\n" +
" roll: !SCQSRoll {\n" +
" length: !int 86400000,\n" +
" format: yyyyMMdd,\n" +
" epoch: 0\n" +
" },\n" +
" indexing: !SCQSIndexing {\n" +
" indexCount: !short 16384,\n" +
" indexSpacing: 16,\n" +
" index2Index: 0,\n" +
" lastIndex: 0\n" +
" },\n" +
" lastAcknowledgedIndexReplicated: -1,\n" +
" recovery: !TimedStoreRecovery {\n" +
" timeStamp: 0\n" +
" }\n" +
"}\n" +
"# position: 344, header: 0\n" +
"--- !!data #binary\n" +
"msg: Hello world\n" +
"# position: 365, header: 1\n" +
"--- !!data #binary\n" +
"msg: Also hello world\n", Wires.fromSizePrefixedBlobs(mappedBytes.readPosition(0)));
我有更多这样做的例子。我不需要为我检查的每一个值写一行,如果格式改变,即使我知道一点。我不希望格式意外更改
注意:我总是把期望值放在第一位
在Java中使用toString()进行单元测试
如果在单元测试中,您的意图是断言对象的所有字段都是相等的,那么被测试对象的toString()
方法必须返回一个字符串,显示所有字段的键值。
但正如您所强调的,在当时,类的字段可能会更改,因此您的toString()
方法可能不会反映实际字段,并且toString()
方法不是为它设计的
测试依赖于文档中未正式定义的toString(),因此可以在将来的任何版本中更改
除了toString()
之外,还有一些替代方法,可以在不使用
约束您在返回所有键值字段的情况下维护toString()
方法,或将toString()
初始意图与断言目的混合使用。
此外,单元测试应记录被测试方法的行为。
以下代码对于预期行为不是自解释的:
assertEquals(someExpression.toString() ,"[a, b, c]");
1) 反射库
想法是将Java反射机制与JUnit断言机制(或您使用的任何测试单元API)混合使用
通过反射,您可以比较来自相同类型的两个对象的每个字段是否相等。其思想如下:构建一条错误文本消息,指示两个字段之间不存在相等性的字段以及原因
当通过反射对所有字段进行比较时,如果错误消息不为null,则使用单元测试机制抛出一个失败异常,其中包含以前生成的相关错误消息。
否则,断言就是成功的。
我经常在我的项目中使用它,当它被证明是相关的时。
您可以自己做,也可以使用类似于Unitils的API来完成这项工作
User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
ReflectionAssert.assertReflectionEquals(user1, user2);
就我个人而言,我创建了自己的库来完成这项工作,可以在断言中添加多个自定义项。
这并不难做到,你可以从Unitils中得到启发
2) 单元测试匹配器库
我认为这是一个更好的选择。
您可以使用Harmcrest或AssertJ。
纯反射更冗长,但它也有很大的优势:
- 它具体记录了被测试方法所期望的行为李>
- 它提供了流畅的断言方法
- 它提供了多个断言特性,以减少单元测试中的模板代码
使用AssertJ,您可以替换以下代码:
assertEquals(foo.toString() ,"[value1=a, value1=b, value1=c]");
其中,foo是定义为以下内容的类的foo实例:
public class Foo{
private String value1;
private String value2;
private String value3;
// getters
}
例如:
Assertions.assertThat(someExpression)
.extracting(Foo::getValue1, Foo::getValue2, Foo::getValue3)
.containsExactly(a, b, c);
如果toString
是CUT API的一部分,那么测试toString
是一个好主意。否则这是一个坏主意。@BoristSpider它不是测试API的一部分。使用toString()是否是一个坏主意,因为它将来可能会更改?如果您的toString()
将来会更改,您也需要更改您的测试。这就是使用单元测试的另一个好处。显然,将列表与其toString进行比较是错误的。如果toString
被记录为返回特定格式,那么应该对其进行测试。如果它只是输出随机调试信息(通常是这样),那么就不应该对它进行测试。但不管怎样,我都很高兴
assertEquals(foo.toString() ,"[value1=a, value1=b, value1=c]");
public class Foo{
private String value1;
private String value2;
private String value3;
// getters
}
Assertions.assertThat(someExpression)
.extracting(Foo::getValue1, Foo::getValue2, Foo::getValue3)
.containsExactly(a, b, c);