繁体   English   中英

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

[英]Using toString() for unit testing in Java

在单元测试中,根据其toString()返回的字符串测试返回值通常是个好主意吗?

例如,执行以下操作以确保返回预期的列表:

 assertEquals(someExpression.toString() ,"[a, b, c]");

在我看来,考虑因素如下:

优点:节省时间(构建实际预期值需要更长的代码)。

缺点:测试依赖于toString(),它在doc中没有正式定义,因此可以在以后的任何版本中进行更改。

我唯一一次测试对象的toString()是因为我有一个不能实现hashcodeequals的不可编辑的类,而是实现了一个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");

Assert.assertThat()方法具有特定于集合的匹配器等等。

例如, contains()适用于集合:

List<String> someExpression = asList("a", "b", "c");
assertThat(someExpression.toString(), contains("a", "b", "c"));

优点是:

  • 您不需要实现.toString() ;
  • 当测试失败时,错误消息非常具有描述性,它会说“预期集合包含'b'并且没有找到它。”

尝试:

import static org.junit.Assert.assertThat;
import static org.hamcrest.Matchers.contains;
import static java.util.Arrays.asList;

List<String> someExpression = asList("a", "c");
assertThat(someExpression.toString(), contains("a", "b", "c"));

我在特定情况下使用toString ,尤其是当等效代码要复杂得多时。 随着您获得更复杂的数据结构,您需要一种快速方法来测试整个结构。 如果您逐个字段地编写要测试的代码,则有可能忘记添加字段。

我的例子在这里https://vanilla-java.github.io/2016/03/23/Microservices-in-the-Chronicle-world-Part-1.html

TopOfBookPrice tobp = new TopOfBookPrice("Symbol", 123456789000L, 1.2345, 1_000_000, 1.235, 2_000_000);
assertEquals("!TopOfBookPrice {\n" +
        "  symbol: Symbol,\n" +
        "  timestamp: 123456789000,\n" +
        "  buyPrice: 1.2345,\n" +
        "  buyQuantity: 1000000.0,\n" +
        "  sellPrice: 1.235,\n" +
        "  sellQuantity: 2000000.0\n" +
        "}\n", tobp.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)));

我有更长的例子,我这​​样做。 我不需要为我检查的每个值写一行,如果格式改变,甚至我知道它的一点点。 我不希望格式出乎意料地改变。

注意:我总是把预期值放在第一位。

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

如果在单元测试中,您的意图是断言对象的所有字段都是等于的,则测试对象的toString()方法必须返回一个字符串,显示所有字段的键值。
但正如你强调的那样,在这个时候,类的字段可以改变,所以你的toString()方法可能不会反映实际的字段,而toString()方法也没有为它设计。

测试依赖于toString(),它在doc中没有正式定义,因此可以在以后的任何版本中进行更改。

toString()还有其他选择,可以在没有的情况下编写漂亮的代码
限制你维护一个toString()方法,返回所有键值字段或混合toString()初始意图用于调试目的与断言目的。
此外,单元测试应记录测试方法的行为。
以下代码对预期行为不能自我解释:

 assertEquals(someExpression.toString() ,"[a, b, c]");

1)反思库

我们的想法是将Java反射机制JUnit断言机制(或您使用的任何测试单元API)混合在一起。
通过反射,您可以比较两个相同类型的对象中的每个字段是否相等。 这个想法如下:您构建一个错误文本消息,指示两个字段之间不遵守相等性的字段以及出于哪些原因。
当通过反射比较所有字段时,如果错误消息不为空,则使用单元测试机制抛出失败异常,其中包含您之前构建的相关错误消息。
否则,断言是成功的。 结果是相关的,我经常在我的项目中使用它。
您可以自己动手,也可以使用像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);

最好覆盖equalshashCode (你自己或者使用lomboks @EqualsAndHashCode )并使用它们比使用toString进行比较。 通过这种方式,您可以明确地进行有关相等性的测试,然后可以重用这些方法。

然后你可以做(​​跟你的列表示例):

assertEquals(
    someExpression,
    Arrays.asList(new Foo("a"), new Foo("b"), new Foo("c"))
);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM