與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