在单元测试中,根据其toString()返回的字符串测试返回值通常是个好主意吗?
例如,执行以下操作以确保返回预期的列表:
assertEquals(someExpression.toString() ,"[a, b, c]");
在我看来,考虑因素如下:
优点:节省时间(构建实际预期值需要更长的代码)。
缺点:测试依赖于toString(),它在doc中没有正式定义,因此可以在以后的任何版本中进行更改。
我唯一一次测试对象的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");
Assert.assertThat()
方法具有特定于集合的匹配器等等。
例如,contains()
适用于馆藏:
List<String> someExpression = asList("a", "b", "c");
assertThat(someExpression.toString(), contains("a", "b", "c"));
优点是:
.toString()
;尝试:
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);
最好覆盖equals
和hashCode
(你自己或者例如使用lomboks @EqualsAndHashCode
)并使用它们比使用toString
进行比较。通过这种方式,您可以明确地进行有关相等性的测试,然后可以重用这些方法。
然后你可以做(跟你的列表示例):
assertEquals(
someExpression,
Arrays.asList(new Foo("a"), new Foo("b"), new Foo("c"))
);