我正在为MyObject
课程编写一个简单的 JUnit 测试。
可以从采用 String 的变量的静态工厂方法创建MyObject
。
MyObject.ofComponents("Uno", "Dos", "Tres");
在MyObject
存在的任何时候,客户都可以通过.getComponents()
以 List<E> 的形式检查其创建的参数方法
myObject.ofComponents(); // -> List<String>: { "Uno", "Dos", "Tres" }
换句话说,MyObject
都会记住并公开使其存在的参数列表。关于这份合同的更多细节:
getComponents
的顺序与为对象创建选择的顺序相同null
上的行为未定义(其他代码保证没有null
到达工厂)我正在编写一个简单的测试,从 String 列表中创建MyObject
,并检查它是否可以通过.getComponents()
返回相同的列表。 我立即执行此操作,但这应该是在实际代码路径中的某个距离。
这是我的尝试:
List<String> argumentComponents = Lists.newArrayList("One", "Two", "Three");
List<String> returnedComponents =
MyObject.ofComponents(
argumentComponents.toArray(new String[argumentComponents.size()]))
.getComponents();
assertTrue(Iterables.elementsEqual(argumentComponents, returnedComponents));
Iterables.elementsEqual()
最佳方式,只要我在构建路径中有库,就可以比较这两个列表吗? 这是我一直在苦恼的事情;我应该使用这个超过Iterable<E> ..检查大小的辅助方法,然后迭代运行.equals()
..或互联网搜索建议的任何其他方法?比较单元测试列表的规范方法是什么? 答案 0 :(得分:69)
为什么不简单地使用List#equals
?
assertEquals(argumentComponents, imapPathComponents);
如果两个列表包含相同顺序的相同元素,则它们被定义为相等。
答案 1 :(得分:50)
我更喜欢使用Hamcrest,因为它可以在出现故障时提供更好的输出
Assert.assertThat(listUnderTest,
IsIterableContainingInOrder.contains(expectedList.toArray()));
而不是报告
expected true, got false
它将报告
expected List containing "1, 2, 3, ..." got list containing "4, 6, 2, ..."
IsIterableContainingInOrder.contain
根据Javadoc:
为Iterables创建一个匹配器,当对检查的Iterable进行单次传递时会产生一系列项目,每个项目在逻辑上等于指定项目中的相应项目。对于肯定匹配,检查的可迭代必须与指定项的数量
具有相同的长度
因此listUnderTest
必须具有相同数量的元素,并且每个元素必须按顺序匹配预期值。
答案 2 :(得分:10)
List实现上的equals()方法应该进行元素比较,所以
assertEquals(argumentComponents, returnedComponents);
更容易。
答案 3 :(得分:9)
org.junit.Assert.assertEquals()
和org.junit.Assert.assertArrayEquals()
完成这项工作。
要避免下一个问题:如果您想忽略订单,请设置所有元素然后进行比较:Assert.assertEquals(new HashSet<String>(one), new HashSet<String>(two))
但是,如果您只是想忽略重复项,但保留您使用LinkedHashSet
列出的订单包装。
又一个提示。技巧Assert.assertEquals(new HashSet<String>(one), new HashSet<String>(two))
正常工作,直到比较失败。在这种情况下,它会向您显示错误消息,其中包含您的集合的字符串表示,这可能会令人困惑,因为集合中的顺序几乎不可预测(至少对于复杂对象而言)。所以,我发现的技巧是使用有序集而不是HashSet
包装集合。您可以将TreeSet
与自定义比较器一起使用。
答案 4 :(得分:5)
为了获得出色的代码可读性,Fest Assertions对asserting lists
提供了很好的支持所以在这种情况下,例如:
Assertions.assertThat(returnedComponents).containsExactly("One", "Two", "Three");
或者将预期的列表添加到数组中,但我更喜欢上述方法,因为它更清晰。
Assertions.assertThat(returnedComponents).containsExactly(argumentComponents.toArray());
答案 5 :(得分:3)
assertTrue()/ assertFalse():仅用于声明返回的布尔结果
assertTrue(Iterables.elementsEqual(argumentComponents, returnComponents));
您要使用Assert.assertTrue()
或Assert.assertFalse()
,因为被测方法返回一个boolean
值。
当方法返回特定的事物(例如List
)时,该事物应包含一些预期的元素,因此以assertTrue()
进行声明:Assert.assertTrue(myActualList.containsAll(myExpectedList)
是反模式。
它使断言易于编写,但由于测试失败,因此也使调试变得困难,因为测试运行器只会对您说:
JUnit4中的预期
true
,但实际为false
Assert.assertEquals(Object, Object)
或JUnit 5中的Assertions.assertIterableEquals(Iterable, Iterable)
:仅当equals()
和toString()
都被(比较对象
这很重要,因为断言中的相等性测试依赖于equals()
,而测试失败消息依赖于比较对象的toString()
。
由于String
会同时覆盖equals()
和toString()
,因此用List<String>
来断言assertEquals(Object,Object)
是完全有效的。
关于此问题:您必须在类中重写equals()
,因为在对象相等性方面它是有意义的,不仅要使使用JUnit进行测试时的断言更容易。
为了使声明更容易,您可以使用其他方法(可以在答案的接下来的部分中看到)。
番石榴是执行/构建单元测试断言的一种方法吗?
如果我的构建路径中有该库来比较这两个列表,那么Google Guava Iterables.elementsEqual()是最好的方法吗?
不,不是。 Guava不是编写单元测试断言的库。
您不需要它来编写大多数(我认为)的单元测试。
比较单元测试列表的规范方法是什么?
作为一个好的实践,我赞成断言/匹配器库。
我不能鼓励JUnit执行特定的断言,因为它提供的功能实在太少且功能有限:它仅执行带有深等号的断言。
有时您希望允许元素中的任何顺序,有时您希望允许期望中的任何元素与实际元素匹配,等等……
因此,使用单元测试断言/匹配器库(例如Hamcrest或AssertJ)是正确的方法。
实际答案提供了Hamcrest解决方案。这是一个AssertJ解决方案。
org.assertj.core.api.ListAssert.containsExactly()
是您所需要的:它验证实际组是否完全包含给定的值,并且没有其他内容(按所述顺序):
验证实际组是否完全包含给定的值,并且 没什么,按顺序。
您的测试可能如下所示:
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
@Test
void ofComponent_AssertJ() throws Exception {
MyObject myObject = MyObject.ofComponents("One", "Two", "Three");
Assertions.assertThat(myObject.getComponents())
.containsExactly("One", "Two", "Three");
}
AssertJ的一个优点是不需要像预期的那样声明List
:它使断言更直接,代码更易读:
Assertions.assertThat(myObject.getComponents())
.containsExactly("One", "Two", "Three");
如果测试失败:
// Fail : Three was not expected
Assertions.assertThat(myObject.getComponents())
.containsExactly("One", "Two");
您会收到一条非常清晰的消息,例如:
java.lang.AssertionError:
期望:
<[“一个”,“两个”,“三个”]>
精确包含(并以相同顺序):
<[“一个”,“两个”]>
但是没有预期的某些元素:
<[“三个”]>
断言/匹配器库是必须的,因为它们确实会更进一步
假设MyObject
不存储String
,而是存储Foo
的实例,例如:
public class MyFooObject {
private List<Foo> values;
@SafeVarargs
public static MyFooObject ofComponents(Foo... values) {
// ...
}
public List<Foo> getComponents(){
return new ArrayList<>(values);
}
}
这是非常普遍的需求。
使用AssertJ,断言仍然很容易编写。更好的是,即使JUnit方式要求元素的类没有覆盖equals()/hashCode()
,您也可以断言列表内容是相等的。
import org.assertj.core.api.Assertions;
import static org.assertj.core.groups.Tuple.tuple;
import org.junit.jupiter.api.Test;
@Test
void ofComponent() throws Exception {
MyFooObject myObject = MyFooObject.ofComponents(new Foo(1, "One"), new Foo(2, "Two"), new Foo(3, "Three"));
Assertions.assertThat(myObject.getComponents())
.extracting(Foo::getId, Foo::getName)
.containsExactly(tuple(1, "One"),
tuple(2, "Two"),
tuple(3, "Three"));
}
答案 6 :(得分:1)
Iterables.elementsEqual
是否是最佳选择的答案: Iterables.elementsEqual
足以比较2 List
s。
Iterables.elementsEqual
用于更一般的场景,它接受更多常规类型:Iterable
。也就是说,您甚至可以将List
与Set
进行比较。 (按迭代顺序,这很重要)
当然ArrayList
和LinkedList
定义相当不错,你可以直接调用equals。当您使用定义不明确的列表时,Iterables.elementsEqual
是最佳选择。有一点需要注意:Iterables.elementsEqual
不接受null
要将List转换为数组:Iterables.toArray
更容易。
对于单元测试,我建议在测试用例中添加空列表。
答案 7 :(得分:0)
有些解决方案是有道理的,我同意它们。但对我来说,我会使用 assertEquals
但我会对两个列表进行排序。
assertEquals(sortedExpectedList, sortedActualList);
它很简单,输出仍然为您提供实际和预期之间的差异。