在Java中使用toString()进行单元测试

在Java中使用toString()进行单元测试,java,unit-testing,tostring,Java,Unit Testing,Tostring,在单元测试中,根据toString()返回的字符串测试返回值通常是一个好主意吗 例如,执行以下操作以确保返回预期列表: assertEquals(someExpression.toString() ,"[a, b, c]"); 在我看来,考虑因素如下: 优点:节省时间(构建实际期望值需要更长的代码) 缺点:该测试依赖于文档中未正式定义的toString(),因此在未来的任何版本中都可能发生更改。最好覆盖等于和哈希代码(自行或使用Lombok),并使用它们进行比较,而不是使用toString。

在单元测试中,根据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);