2016-07-30 22 views
2

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

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

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

在我看来,该事项如下:

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

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

+1

如果'toString'是CUT的API的一部分,那么测试'toString'是个好主意。否则,这是一个坏主意。 –

+0

@BoristheSpider它不是测试API的一部分。使用toString()是否是一个坏主意,因为它可以在将来改变? – Ofer

+0

如果你的'toString()'将来会改变,你也需要改变你的测试。这就是使用单元测试的另一个好处。显然,将列表与其toString进行比较是有缺陷的 –

回答

4

,我将测试上的对象的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"); 
+0

“硬编码的预期结果” - 你是什么意思?我以任何方式对预期结果进行硬编码。你的意思是我从toString()硬编码期望的字符串,这是一个坏主意,因为(主要)它从来没有正式的定义? – Ofer

+0

好吧,测试都是关于硬编码过程的预期结果。但是,是的,我的意思是硬编码的字符串。对于那个很抱歉。 一般来说,从toString()对期望的字符串进行硬编码是一个坏主意,因为您将它从当前实现的预期结果中提取出来,该预期结果可能(并且会在一段时间后)改变。 它也可能容易出错,因为测试创建者可能会因为它是预期的字符串而放置正确的预期字符串。通过另一个对象的toString()来测试对象的toString()方法更加可靠。 – Kree

+0

好的,我无法编辑我以前的评论。通过“......不妨把正确的预期字符串放在正确的位置,因为它是预期的字符串。”我的意思是硬编码字符串不存在根据,除非它是预期的结果。如果toString突然改变了,你还必须改变测试,而在断言中使用对象的toString()对维护测试的完整性没有任何问题。现在我想到了,这不是真正的toString()被记录或不记录的问题。 – Kree

0

这是更好地覆盖equalshashCode(自己或例如使用lomboks @EqualsAndHashCode为),做使用它们比使用toString比较。通过这种方式,您可以明确地进行关于平等的测试,之后您可以重新使用这些方法

然后你可以做(​​以下与列表中为例):

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

但假设构建对象需要更多的代码,那么使用toString()是否合法? (即使在一个标准的Java类的实例(如LinkedList) - 它的toString()没有记录) – Ofer

1

Assert.assertThat()方法有具体的集合和更多的匹配。

例如,作品类别:

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

的优点是:

  • 你并不需要实现.toString();
  • 当测试失败时,错误信息非常具有描述性,它会说“Expected collection containig'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")); 
+0

谢谢,列表只是一个例子。使用toString()而不是构建一个对象时通常合理,因为它可以节省大量代码? – Ofer

+1

你可以使用它,如果它可以节省你*很多时间。但它是一种脆弱的方法('.toString()'本身可能有错误并产生误报!)。但总的来说,我会面临这种需求/困难,作为其他设计问题的可能迹象。为什么现有的API不够?为什么你需要检查这么多的信息,是不是太多的实施细节?等等。没有什么是写在石头上的,所以你可以使用它,但我不会为每个案例或新手提供建议 - 你必须真正理解它是一个次优的方法(有缺陷),然后才能使用它。 – acdcjunior

1

在特定情况下,我使用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中很容易看到,因为它产生

Display Comparison

当你获得更复杂的例子,检查每(嵌套)值,修正它,当它打破实在是乏味的更新版本。一个更长的例子是

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))); 

我有更长的例子,我这​​样做。我不需要为我检查的每一个值写一行,如果格式改变了,即使有一点我知道。我不希望格式意外改变。

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

1

在Java中

使用的单元测试的toString()如果你的单元测试,你的目的是要断言对象的所有字段是平等的,你的对象的测试必须在toString()方法所以返回一个字符串,显示所有字段的键值。
但是正如你强调的那样,在这段时间里,这个类的字段可能会改变,所以你的toString()方法可能不会反映实际字段,并且toString()方法不是为它设计的。

测试依赖于toString(),它在文档中没有正式定义,因此可以在将来的任何版本中更改。

有替代toString()允许写漂亮的代码不
约束你保持toString()方法与所有键值领域退回或混合toString()初衷与目的断言调试的目的。
此外,单元测试应记录测试方法的行为。
A码为下列不上预期的行为自我解释:

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);