与Hamcrest 1.2相比 ,针对Matchers类的Hamcrest 1.3 Javadoc文档为该类的几种方法添加了更多文档。 例如,四个重载的contains方法具有更具描述性的Javadoc文档,如下面所示的两个比较屏幕快照所示。
尽管仅通过尝试就可以弄清楚“包含”匹配器的工作方式,但是Hamcrest 1.3中的Javadoc使得阅读它们的工作方式更加容易。 大多数Java开发人员在考虑contains()
方法时,可能会想到类似String.contains(CharSequence)或Collection.contains(Object)的行为。 换句话说,大多数Java开发人员可能将“包含”描述为描述String / Collection是否包含提供的字符/对象以及其他可能的字符/对象。 但是,对于Hamcrest匹配器,“包含”具有更具体的含义。 随着Hamcrest 1.3文档更加清晰明了,“包含”匹配项对传递给这些方法的项目数量和项目顺序更加敏感。
此处显示的示例使用JUnit和Hamcrest。 这里要强调的是,Hamcrest的JAR文件必须在JUnit的JAR文件之前出现在单元测试的类路径中,否则我必须使用为与独立的Hamcrest JAR一起使用而构建的“特殊” JUnit JAR文件。 使用这些方法之一可以避免NoSuchMethodError和其他错误(例如org.hamcrest.Matcher.describeMismatch错误),这是由类的版本不匹配导致的。 我已经在JUnit的博客超越核心Hamcrest中撰写了有关JUnit / Hamcrest细微差别的文章。
接下来的两个屏幕快照指示了单元测试代码段的结果(如NetBeans 7.3所示),我将在稍后的博客中展示这些单元测试代码段,以演示包含匹配器的Hamcrest。 这些测试应该有一些失败(7个测试通过,4个测试失败),以使Hamcrest匹配器在不阅读Javadoc的情况下可能无法按预期工作的地方很明显。 第一张图片仅显示5个测试通过,2个测试失败和4个测试导致错误。 这是因为在NetBeans项目的“测试库”类路径中,在Hamcrest之前列出了JUnit。 第二个图像显示了预期的结果,因为Hamcrest JAR发生在项目的“测试库”类路径中的JUnit JAR之前。
为了演示的目的,我有一个简单的人为设计的类要测试。 接下来显示该Main
类的源代码。
Main.java
package dustin.examples;import java.util.Collections;
import java.util.HashSet;
import java.util.Set;/*** Main class to be unit tested.* * @author Dustin*/
public class Main
{/** Uses Java 7's diamond operator. */private Set<String> strings = new HashSet<>();public Main() {}public boolean addString(final String newString){return this.strings.add(newString);}public Set<String> getStrings(){return Collections.unmodifiableSet(this.strings);}
}
显示了要测试的类之后,现在该考虑使用Hamcrest匹配器构建一些基于JUnit的测试了。 具体来说,测试是为了确保通过类的addString(String)
方法添加的addString(String)
位于其基础Set
并且可以通过getStrings()
方法访问。 接下来显示的单元测试方法演示了如何适当地使用Hamcrest匹配器来确定类的基础Set
是否包含添加的字符串。
在Set Works中将Hamcrest contains()匹配器与单个字符串一起使用
/*** This test will pass because there is only a single String and so it will* contain that single String and order will be correct by implication.*/@Testpublic void testAddStringAndGetStringsWithContainsForSingleStringSoWorks(){final Main subject = new Main();final boolean resultJava = subject.addString('Java');final Set<String> strings = subject.getStrings();assertThat(strings, contains('Java'));}
上面显示的单元测试通过了,因为Set
仅包含一个字符串,因此使用contains
匹配项进行测试的字符串的顺序和数量匹配。
如果订单匹配,则使用具有相同数量元素的Hamcrest容器有效
/*** The 'contains' matcher expects exact ordering, which really means it should* not be used in conjunction with {@code Set}s. Typically, either this method* will work and the method with same name and '2' on end will not work or* vice versa.*/@Testpublic void testAddStringAndGetStringsWithContainsForMultipleStringsNotWorks1(){final Main subject = new Main();final boolean resultJava = subject.addString('Java');final boolean resultGroovy = subject.addString('Groovy');final Set<String> strings = subject.getStrings();assertThat(strings, contains('Java', 'Groovy'));}/*** The 'contains' matcher expects exact ordering, which really means it should* not be used in conjunction with {@code Set}s. Typically, either this method* will work and the method with same name and '1' on end will not work or* vice versa.*/@Testpublic void testAddStringAndGetStringsWithContainsForMultipleStringsNotWorks2(){final Main subject = new Main();final boolean resultJava = subject.addString('Java');final boolean resultGroovy = subject.addString('Groovy');final Set<String> strings = subject.getStrings();assertThat(strings, contains('Groovy', 'Java'));}
上面显示的两个示例单元测试以及运行这些测试的结果输出(如上一个屏幕快照所示)显示,只要contains()
匹配器的参数数量与要测试的集合中的Strings数量相同, , 如果所测试的元素与集合中的元素完全相同的顺序,则匹配可能有效。 对于无序的Set
,不能依赖此顺序,因此contains()
不太可能是与多个元素的Set
上的单元测试一起使用的良好匹配器。
使用具有不同数量元素的Hamcrest容器永远行不通
/*** Demonstrate that contains will NOT pass when there is a different number* of elements asked about contains than in the collection.*/@Testpublic void testAddStringAndGetStringsWithContainsNotWorksDifferentNumberElements1(){final Main subject = new Main();final boolean resultJava = subject.addString('Java');final boolean resultGroovy = subject.addString('Groovy');final Set<String> strings = subject.getStrings();assertThat(strings, contains('Java'));}/*** Demonstrate that contains will NOT pass when there is a different number* of elements asked about contains than in the collection even when in* different order.*/@Testpublic void testAddStringAndGetStringsWithContainsNotWorksDifferentNumberElements2(){final Main subject = new Main();final boolean resultJava = subject.addString('Java');final boolean resultGroovy = subject.addString('Groovy');final Set<String> strings = subject.getStrings();assertThat(strings, contains('Groovy'));}
作为JUnit测试结果表明,这两个单元测试从未通过,因为在被测试元件的数目Set
为比在元件的数量较少的Set
。 换句话说,这证明了contains()
匹配器不会简单地测试集合中的给定元素:它会测试所有指定元素的存在和指定顺序。 在某些情况下,这可能太过局限了,所以现在我将继续进行Hamcrest提供的其他一些确定项,以确定特定集合中是否包含元素。
使用Hamcrest的containsInAnyOrder()匹配器
containsInAnyOrder
匹配器不如contains()
匹配器严格:它允许被测试的元素以任何顺序通过包含集合中的元素。
/*** Test of addString and getStrings methods of class Main using Hamcrest* matcher containsInAnyOrder.*/@Testpublic void testAddStringAndGetStringsWithContainsInAnyOrder(){final Main subject = new Main();final boolean resultJava = subject.addString('Java');final boolean resultCSharp = subject.addString('C#');final boolean resultGroovy = subject.addString('Groovy');final boolean resultScala = subject.addString('Scala');final boolean resultClojure = subject.addString('Clojure');final Set<String> strings = subject.getStrings();assertThat(strings, containsInAnyOrder('Java', 'C#', 'Groovy', 'Scala', 'Clojure'));}/*** Use containsInAnyOrder and show that order does not matter as long as* all entries provided are in the collection in some order.*/@Testpublic void testAddStringAndGetStringsWithContainsInAnyOrderAgain(){final Main subject = new Main();final boolean resultJava = subject.addString('Java');final boolean resultGroovy = subject.addString('Groovy');final Set<String> strings = subject.getStrings();assertThat(strings, containsInAnyOrder('Java', 'Groovy'));assertThat(strings, containsInAnyOrder('Groovy', 'Java'));}
上方显示的两个单元测试都通过了,尽管被测试的字符串以与两个集合中可能存在的顺序不同的顺序提供给containsInAnyOrder()
匹配器。 但是,不太严格的containsInAnyOrder()
匹配器仍要求将包含集合的所有元素指定为传递。 由于不满足此条件,因此以下单元测试未通过。
/*** This will fail because containsInAnyOrder requires all items to be matched* even if in different order. With only one element being tried and two* elements in the collection, it will still fail. In other words, order* does not matter with containsInAnyOrder, but all elements in the collection* still need to be passed to the containsInAnyOrder matcher, just not in the* exact same order.*/@Testpublic void testAddStringAndGetStringsWithContainsInAnyOrderDiffNumberElements(){final Main subject = new Main();final boolean resultJava = subject.addString('Java');final boolean resultGroovy = subject.addString('Groovy');final Set<String> strings = subject.getStrings();assertThat(strings, containsInAnyOrder('Java'));}
Hamcrest hasItem()和hasItems()匹配器像声音一样工作
如接下来的两个单元测试方法(均通过)所示,Hamcrest hasItem()
(用于单个项目)和hasItems
(用于多个项目)成功地测试了一个集合分别具有一个或多个指定项目,而无需考虑用于指定项目的订单或数量。 这实际上更像大多数Java开发人员在处理Strings和collections时“包含”工作。
/*** Demonstrate hasItem() will also work for determining a collection contains* a particular item.*/@Testpublic void testAddStringAndGetStringsWithHasItem(){final Main subject = new Main();final boolean resultJava = subject.addString('Java');final boolean resultGroovy = subject.addString('Groovy');final Set<String> strings = subject.getStrings();assertThat(strings, hasItem('Groovy'));assertThat(strings, hasItem('Java'));}/*** Demonstrate that hasItems works for determining that a collection has one* or more items and that the number of items and the order of the items* is not significant in determining pass/failure.*/@Testpublic void testAddStringAndGetStringsWithHasItems(){final Main subject = new Main();final boolean resultJava = subject.addString('Java');final boolean resultGroovy = subject.addString('Groovy');final Set<String> strings = subject.getStrings();assertThat(strings, hasItems('Groovy', 'Java'));assertThat(strings, hasItems('Java', 'Groovy'));assertThat(strings, hasItems('Groovy'));assertThat(strings, hasItems('Java'));}
Hamcrest isIn()匹配器从其他方向测试遏制
刚刚讨论过的hasItem()
和hasItems()
匹配器不如contains()
严格,甚至不如containsInAnyOrder()
严格,并且经常是人们想要简单地确保一个或多个项目在集合中某处而又没有的情况下想要的关注该集合中的项目顺序或该集合中其他可能的项目。 使用Hamcrest确定相同关系的另一种方法是使用isIn
匹配器,但从相反的角度来看。 isIn
匹配器确定某个项目是否位于提供给匹配器的集合的某个位置,而无需考虑该项目在集合中的顺序,或者不考虑该集合中是否还有其他项目。
/*** Use isIn matcher to test individual element is in provided collection.*/@Testpublic void testAddStringAndGetStringsWithIsIn(){final Main subject = new Main();final boolean resultJava = subject.addString('Java');final boolean resultGroovy = subject.addString('Groovy');final Set<String> strings = subject.getStrings();assertThat('Groovy', isIn(strings));assertThat('Java', isIn(strings));}
结论
Hamcrest提供了一组丰富的匹配器,可用于确定指定的元素是否驻留在指定的集合中。 在决定应用这些和确定使用哪个时,请记住以下重要点:
- 确保Hamcrest JAR在JUnit JAR之前位于测试类路径上。
- 使用
contains
当你想确保集合包含了所有规定的项目,没有其他物品,你想收集包含指定顺序的项目。- 通常应避免对
Set
s使用contains()
匹配器,因为它们本质上是无序的。
- 通常应避免对
- 当您仍要严格测试是否在测试中指定的集合中存在完全相同的项目时,请使用
containsInAnyOrder
匹配器,但不必关心顺序(适用于Set
)。 - 使用
hasItem()
和hasItems()
匹配器询问集合是否包含(可能在其他未列出的项目中,并且没有特定的顺序)指定的项目。 - 使用
isIn()
匹配器询问特定项是否在指定的集合中,而与其他项是否在该集合中或该项在包含的集合中的顺序无关。
参考:来自我们的JCG合作伙伴 Dustin Marx的Hamcrest包含匹配器 ,位于Inspired by Actual Events博客上。
翻译自: https://www.javacodegeeks.com/2013/01/hamcrest-containing-matchers.html