上下文
我写一个简单的JUnit用于测试MyObject
类。
阿MyObject
可以从静态工厂方法,该方法的可变参数来创建字符串 。
MyObject.ofComponents("Uno", "Dos", "Tres");
在的存在期间的任何时间MyObject
,客户端可以检查它通过在的形式创建的参数列表<E>通过.getComponents()
方法。
myObject.ofComponents(); // -> List<String>: { "Uno", "Dos", "Tres" }
换句话说,一个MyObject
都记住并自曝该将其带入存在的参数列表。 有关本合同的更多细节:
- 的顺序
getComponents
将是相同的作为选择的对象创建一个 - 重复随后的字符串允许组分并保持在顺序
- 对行为
null
未定义(其它代码,保证无null
到达工厂) - 有没有办法来改变组件的对象实例化后的列表
我写一个简单的测试,创建一个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()
的最好方法,只要我有库在我的构建路径,那些两个列表比较? 这是我一直折腾有关; 我应该使用这个辅助方法,它越过了可迭代<E> 。检查大小,然后重复运行.equals()
..或任何其他的,一个互联网搜索建议的方法是什么? 什么是比较列出了单元测试的典型方式是什么?
可选的见解我很想得到
- 是方法试验设计合理? 我不是专家的JUnit !
- 是
.toArray()
的转换的最佳途径名单<E>到E的可变参数?
我更喜欢使用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
Hamcrest
据的Javadoc:
创建用于Iterables匹配器匹配当单个传过来的检查可迭代产生的一系列项目,每个逻辑上等于相应项目中指定项。 对于正的比赛,该检查可迭代必须是相同长度的所规定的项目数
因此listUnderTest
必须具有相同数量的元素,并且每个元素必须以预期的值相匹配。
为什么不直接使用List#equals
?
assertEquals(argumentComponents, imapPathComponents);
合同List#equals
:
两个列表被定义为等于如果它们包含以相同的顺序相同的元件。
在你的列表实现equals()方法应该做的elementwise比较,所以
assertEquals(argumentComponents, returnedComponents);
是一个容易得多。
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
自定义比较。
对于优秀的代码的可读性, 巨星断言有很好的支持断言名单
所以在这种情况下,是这样的:
Assertions.assertThat(returnedComponents).containsExactly("One", "Two", "Three");
或使预期的列表到一个数组,但我更喜欢上面的方法,因为它更清楚。
Assertions.assertThat(returnedComponents).containsExactly(argumentComponents.toArray());
assertTrue()/ assertFalse():只用断言返回布尔结果
assertTrue(Iterables.elementsEqual(argumentComponents,returnedComponents));
你想用Assert.assertTrue()
或Assert.assertFalse()
来作为测试方法返回一个boolean
值。
由于该方法返回一个具体的事情,如List
应该包含一些预期的元素,断言assertTrue()
以这样的方式Assert.assertTrue(myActualList.containsAll(myExpectedList)
是一种反模式。
它使断言容易写,但作为测试失败,这也使得它很难调试,因为测试运行,只会给你喜欢的东西说:
预计true
,但实际是false
Assert.assertEquals(Object, Object)
在JUnit4或Assertions.assertIterableEquals(Iterable, Iterable)
中的JUnit 5:只使用既是equals()
和toString()
所比较的对象被overrided为类(和深)
这很重要,因为声明中的平等的测试依赖于equals()
和测试失败消息依赖toString()
比较的对象。
作为String
覆盖两者equals()
和toString()
它是完全有效的断言List<String>
用assertEquals(Object,Object)
。 而这件事情:你必须覆盖equals()
中的一类,因为它的对象平等的条件下才有意义,不仅使断言更容易使用JUnit测试。
为了让断言你越有其他的方式(你可以在答案的下一个点看)。
是番石榴执行/建立单元测试断言的方法吗?
是谷歌番石榴Iterables.elementsEqual()的最好方法,只要我有库在我的构建路径,那些两个列表比较?
不它不是。 番石榴是不写单元测试断言一个库。
你不需要它来写单元测试的大多数(所有我认为)。
什么是比较列出了单元测试的典型方式是什么?
作为一个很好的做法我赞成断言/匹配库。
我不能鼓励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断言仍是简单的写。 你可以更好的断言,列表内容是即使类的元素不会覆盖相等equals()/hashCode()
,而JUnit的方式要求:
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"));
}
- 我是否回答
Iterables.elementsEqual
是最好的选择:
Iterables.elementsEqual
足以比较2 List
秒。
Iterables.elementsEqual
在更一般的情况下使用,它接受更多的普通型: Iterable
。 也就是说,你甚至可以比较一个List
与Set
。 (通过迭代顺序,这一点很重要)
当然ArrayList
和LinkedList
定义equals相当不错的,你可以直接调用等于。 当你使用一个没有很好地界定名单虽然, Iterables.elementsEqual
是最好的选择。 有一点应该注意: Iterables.elementsEqual
不接受null