定制Hamcrest Matchers
本文是我們名為“ 用Mockito進行測試 ”的學院課程的一部分。
在本課程中,您將深入了解Mockito的魔力。 您將了解有關(guān)“模擬”,“間諜”和“部分模擬”的信息,以及它們相應(yīng)的Stubbing行為。 您還將看到使用測試雙打和對象匹配器進行驗證的過程。 最后,討論了使用Mockito的測試驅(qū)動開發(fā)(TDD),以了解該庫如何適合TDD的概念。 在這里查看 !
在本教程中,我們將使用Hamcrest API創(chuàng)建自己的自定義匹配器,以擴展Hamcrest提供的“開箱即用”功能。
目錄
1.為什么要使用自定義匹配器? 2.匹配器的解剖 3.現(xiàn)有課程的自定義匹配器1.為什么要使用自定義匹配器?
我們會不時遇到Matchers Hamcrest庫的限制。 我們需要在標準類(例如字符串,整數(shù)或列表)上具有新的匹配器功能,或者需要創(chuàng)建與已創(chuàng)建的高度自定義類匹配的匹配器。 在本教程中,我們將使用Hamcrest提供的工具針對這兩種情況創(chuàng)建匹配器。
2.匹配器的解剖
為了創(chuàng)建自定義匹配器,我們將擴展內(nèi)置的Abstract類TypeSafeDiagnosingMatcher 。 如果在IDE中將此類擴展為Integer類型,您將看到該類的兩個抽象方法:
public class BlankMatcher extends TypeSafeDiagnosingMatcher<Integer> {@Overrideprotected boolean matchesSafely(Integer integer, Description description) {return false;}@Overridepublic void describeTo(Description description) {} }第一個方法matchesSafely()是Matcher的作用所在,這是Hamcrest在要使用Matcher測試值時執(zhí)行的方法。 如果是這種情況,它還負責報告Matcher為何不匹配。 不匹配描述是Hamcrest在失敗匹配器輸出的“但是:”部分之后使用的描述,因此不匹配描述應(yīng)相應(yīng)地設(shè)置格式。
第二種方法describeTo()用于生成匹配器正在檢查的內(nèi)容的描述。 Hamcrest在失敗的匹配器的輸出的“ Expected:”部分之后使用此描述,因此該描述應(yīng)相應(yīng)設(shè)置格式。
在后臺,此Matcher將檢查輸入值是否不為null且類型正確,因此在執(zhí)行命中我們的matchesSafely方法時,我們保證具有正確類型的值以進行匹配。
Hamcrest為我們完成了繁重的工作,因此這兩種方法都是實現(xiàn)我們自己的Hamcrest Matchers所需要的。 在接下來的示例中,我們還將添加一些語法糖,以簡化創(chuàng)建和使用自定義匹配器的過程。
3.現(xiàn)有課程的自定義匹配器
在本節(jié)中,我們將創(chuàng)建許多可用于現(xiàn)有類型的自定義匹配器。
甚至()
讓我們從創(chuàng)建匹配器開始,以確定輸入數(shù)字是否為偶數(shù)。 和以前一樣,我們將為整數(shù)擴展TypeSafeDiagnosingMatcher 。
public class IsEven extends TypeSafeDiagnosingMatcher<Integer> {@Overrideprotected boolean matchesSafely(Integer integer, Description description) {return false;}@Overridepublic void describeTo(Description description) {} }然后,我們將實現(xiàn)describeTo()方法,以提供對匹配器的期望描述:
@Override public void describeTo(Description description) {description.appendText("An Even number"); }我們可以使用description參數(shù)來創(chuàng)建Matcher的描述。 Description類提供了許多用于格式化輸入的輔助方法,在這種情況下,我們使用appendText()方法簡單地添加一些文本。
接下來,讓我們使用傳遞到matchesSafely方法的description參數(shù)來創(chuàng)建錯誤消息。 請記住,只有在故障情況下才能看到此輸出。 我們將在Description類中使用幾種方法來格式化消息:
@Override protected boolean matchesSafely(Integer integer, Description description) {description.appendText("was ").appendValue(integer).appendText(", which is an Odd number");return false; }接下來,我們將對數(shù)字進行實際檢查,以查看數(shù)字是否為偶數(shù):
@Override protected boolean matchesSafely(Integer integer, Description description) {description.appendText("was ").appendValue(integer).appendText(", which is an Odd number");return integer % 2 == 0; }最后,我們將向類添加靜態(tài)工廠方法以使其易于在測試中使用:
public static IsEven isEven() {return new IsEven(); }放在一起,我們有以下Matcher類:
public class IsEven extends TypeSafeDiagnosingMatcher<Integer> {@Overrideprotected boolean matchesSafely(Integer integer, Description description) {description.appendText("was ").appendValue(integer).appendText(", which is an Odd number");return integer % 2 == 0;}@Overridepublic void describeTo(Description description) {description.appendText("An Even number");}public static IsEven isEven() {return new IsEven();} }現(xiàn)在,我們可以使用新的匹配器編寫一些測試。
我們創(chuàng)建一個名為IsEvenTest的新Test類,然后導(dǎo)入新的工廠方法:
import static com.javacodegeeks.hughwphamill.mockito.hamcrest.matchers.IsEven.isEven;public class IsEvenTest {}接下來,我們將編寫一種測試方法來測試匹配器評估為true的肯定情況。
@Test public void should_pass_for_even_number() throws Exception {// GivenInteger test = 4;// ThenassertThat(test, isEven()); }還有一種顯示匹配器無法匹配的輸出的方法。
@Test public void should_fail_for_odd_number() throws Exception {// GivenInteger test = 5;// ThenassertThat(test, isEven()); }這將生成以下輸出:
java.lang.AssertionError: Expected: An Even numberbut: was <5>, which is an Odd number通常,在為匹配器編寫真實測試時,我們希望通過測試以確保邏輯是正確的,畢竟,我們不想在測試失敗的項目上工作! 如果我們想這樣做,我們可以將斷言更改為以下內(nèi)容:
assertThat(test, not(isEven()));但是,在開發(fā)過程中編寫失敗的測試以手動檢查匹配器的輸出可能會很有用。
divisibleBy(整數(shù)除數(shù))
現(xiàn)在我們已經(jīng)看到了如何創(chuàng)建一個自定義Matcher來測試Integer的固有屬性。 是奇數(shù)還是偶數(shù)? 但是,我們在hamcrest Matcher庫中看到許多Matchers會根據(jù)輸入值進行測試。 現(xiàn)在,我們將自己創(chuàng)建一個Matcher。
想象一下,我們想定期測試數(shù)字以發(fā)現(xiàn)它們是否可以被另一個數(shù)字整除。 我們可以編寫一個自定義Matcher為我們完成此任務(wù)。
首先,像上一個示例一樣,創(chuàng)建一個無參匹配器,并將除數(shù)硬編碼為3。
public class DivisibleBy extends TypeSafeDiagnosingMatcher<Integer> {@Overrideprotected boolean matchesSafely(Integer integer, Description description) {int remainder = integer % 3; // Hardcoded to 3 for now!description.appendText("was ").appendValue(integer).appendText(" which left a remainder of ").appendValue(remainder);return remainder == 0;}@Overridepublic void describeTo(Description description) {description.appendText("A number divisible by 3"); // Hardcoded to 3 for now!}public static DivisibleBy divisibleBy() {return new DivisibleBy();} }我們的Matcher看起來很像我們的IsEven() Matcher,但是不匹配的描述稍微復(fù)雜一些。
我們?nèi)绾螐挠簿幋a值更改為輸入值? 沒什么花招,實際上就像添加一個私有成員變量并將其設(shè)置在構(gòu)造函數(shù)中,然后將值通過factory方法傳遞一樣容易。
現(xiàn)在讓我們看一下完整的Matcher:
public class DivisibleBy extends TypeSafeDiagnosingMatcher<Integer> {private final Integer divisor;public DivisibleBy(Integer divisor) {this.divisor = divisor;}@Overrideprotected boolean matchesSafely(Integer integer, Description description) {int remainder = integer % 3; // Hardcoded to 3 for now!description.appendText("was ").appendValue(integer).appendText(" which left a remainder of ").appendValue(remainder);return remainder == 0;}@Overridepublic void describeTo(Description description) {description.appendText("A number divisible by 3"); // Hardcoded to 3 for now!}public static DivisibleBy divisibleBy(Integer divisor) {return new DivisibleBy(divisor);} }再次,讓我們創(chuàng)建一些測試來練習我們的新Matcher:
public class DivisibleByTest {@Testpublic void should_pass_for_true_divisor() throws Exception {// GivenInteger test = 15;// ThenassertThat(test, is(divisibleBy(5)));}@Testpublic void should_fail_for_non_divisor() throws Exception {// GivenInteger test = 17;// ThenassertThat(test, is(divisibleBy(3)));} }測試失敗的輸出是
java.lang.AssertionError: Expected: is A number divisible by 3but: was <17> which left a remainder of <2>現(xiàn)在,我們已經(jīng)看到了如何為現(xiàn)有類創(chuàng)建自定義匹配器,這些類可以測試內(nèi)部屬性或接受測試值。
接下來,我們將為自己編寫的類創(chuàng)建自定義匹配器。
4.針對您自己的班級的自定義匹配器
當我們使用自己創(chuàng)建的類進行工作時,自定義Hamcrest匹配器是測試庫中的強大工具。 現(xiàn)在,我們將創(chuàng)建一個域模型,并編寫一些自定義匹配器以使用該模型。
我們的模型:一棵樹
編程中常見的數(shù)據(jù)結(jié)構(gòu)是由許多節(jié)點組成的樹。 這些節(jié)點可能只有一個父節(jié)點或一個或多個子節(jié)點。 沒有父節(jié)點的節(jié)點稱為根。 沒有子節(jié)點的節(jié)點稱為葉子。 如果可以通過X的父級從X到Y(jié)跟蹤路徑,則將節(jié)點X視為另一個節(jié)點Y的后代。如果Y是X的后代,則將節(jié)點X視為另一個節(jié)點Y的祖先。如果X和Y都共享父節(jié)點,則視為另一個節(jié)點Y的同級。
我們將使用Node存儲單個int值。
我們的模型將有一個名為Node的類,如下所示:
/** * Node class for building trees ** Uses instance equality. */ public class Node {private final int value;private Node parent;private final Set<Node> children;/*** Create a new Node with the input value*/public Node(int value) {this.value = value;children = new HashSet<>();}/*** @return The value of this Node*/public int value() {return value;}/*** @return The parent of this Node*/public Node parent() {return parent;}/*** @return A copy of the Set of children of this Node*/public Set<Node> children() {return new HashSet<>(children);}/*** Add a child to this Node** @return this Node*/public Node add(Node child) {if (child != null) {children.add(child);child.parent = this;}return this;}/*** Remove a child from this Node** @return this Node*/public Node remove(Node child) {if (child != null && children.contains(child)) {children.remove(child);child.parent = null;}return this;}public String toString() {StringBuilder builder = new StringBuilder();builder.append("Node{").append("value=").append(value).append(",").append("parent=").append(parent != null ?parent.value : "null").append(",").append("children=").append("[").append(children.stream().map(n -> Integer.toString(n.value)).collect(Collectors.joining(","))).append("]}");return builder.toString();} }請注意,對于這個簡單的示例,我們的類不是線程安全的。
該模型使我們能夠構(gòu)建一個節(jié)點樹,從根節(jié)點開始并添加子節(jié)點。 我們可以在下面的小型應(yīng)用程序類中看到這一點:
public class App {public static void main(String... args) {Node root = createTree();printNode(root);}private static Node createTree() {/*1/ \2 3/ \ / \4 5 6 7/ \ |8 9 10*/Node root = new Node(1);root.add(new Node(2).add(new Node(4).add(new Node(8)).add(new Node(9))).add(new Node(5).add(new Node(10)))).add(new Node(3).add(new Node(6)).add(new Node(7)));return root;}private static void printNode(Node node) {System.out.println(node);for (Node child : node.children()) {printNode(child);}} }這將產(chǎn)生以下輸出:
Node{value=1,parent=null,children=[3,2]} Node{value=3,parent=1,children=[7,6]} Node{value=7,parent=3,children=[]} Node{value=6,parent=3,children=[]} Node{value=2,parent=1,children=[5,4]} Node{value=5,parent=2,children=[10]} Node{value=10,parent=5,children=[]} Node{value=4,parent=2,children=[8,9]} Node{value=8,parent=4,children=[]} Node{value=9,parent=4,children=[]}現(xiàn)在我們已經(jīng)定義了模型并知道如何使用它,我們可以開始針對它創(chuàng)建一些Matchers。
我們將在類中使用Node測試治具,以針對一致的模型進行測試,該模型將與示例應(yīng)用程序中定義的樹結(jié)構(gòu)相同。 夾具在這里定義:
public class NodeTestFixture {static Node one = new Node(1);static Node two = new Node(2);static Node three = new Node(3);static Node four = new Node(4);static Node five = new Node(5);static Node six = new Node(6);static Node seven = new Node(7);static Node eight = new Node(8);static Node nine = new Node(9);static Node ten = new Node(10);static {one.add(two);one.add(three);two.add(four);two.add(five);three.add(six);three.add(seven);four.add(eight);four.add(nine);five.add(ten);} }葉()
我們將創(chuàng)建的第一個Matcher將檢查輸入節(jié)點是否為葉節(jié)點。 它將通過檢查輸入節(jié)點是否有任何子節(jié)點來完成此操作,如果有則子節(jié)點不是葉節(jié)點。
public class IsLeaf extends TypeSafeDiagnosingMatcher<Node> {@Overrideprotected boolean matchesSafely(Node node, Description mismatchDescription) {if (!node.children().isEmpty()) {mismatchDescription.appendText("a node with ").appendValue(node.children().size()).appendText(" children");return false;}return true;}@Overridepublic void describeTo(Description description) {description.appendText("a leaf node with no children");}public static IsLeaf leaf() {return new IsLeaf();} }測試類別:
public class IsLeafTest extends NodeTestFixture {@Testpublic void should_pass_for_leaf_node() throws Exception {// GivenNode node = NodeTestFixture.seven;// ThenassertThat(node, is(leaf()));}@Testpublic void should_fail_for_non_leaf_node() throws Exception {// GivenNode node = NodeTestFixture.four;// ThenassertThat(node, is(not(leaf())));} }根()
現(xiàn)在,我們將創(chuàng)建一個匹配器,以檢查節(jié)點是否為根節(jié)點。 我們將通過檢查父節(jié)點的存在來做到這一點。
public class IsRoot extends TypeSafeDiagnosingMatcher<Node> {@Overrideprotected boolean matchesSafely(Node node, Description mismatchDescription) {if (node.parent() != null) {mismatchDescription.appendText("a node with parent ").appendValue(node.parent());return false;}return true;}@Overridepublic void describeTo(Description description) {description.appendText("a root node with no parent");}public static IsRoot root() {return new IsRoot();} }測試類別:
public class IsRootTest {@Testpublic void should_pass_for_root_node() throws Exception {// GivenNode node = NodeTestFixture.one;// ThenassertThat(node, is(root()));}@Testpublic void should_fail_for_non_root_node() throws Exception {// GivenNode node = NodeTestFixture.five;// ThenassertThat(node, is(not(root())));} }后裔Of(節(jié)點節(jié)點)
接下來是帶有輸入的匹配器,我們將檢查給定的Node是否為輸入Node的后代。 我們將向上移動父母,看看是否在根之前到達了測試節(jié)點。
public class IsDescendant extends TypeSafeDiagnosingMatcher<Node> {private final Node ancestor;public IsDescendant(Node ancestor) {this.ancestor = ancestor;}@Overrideprotected boolean matchesSafely(Node node, Description description) {while (node.parent() != null) {if (node.parent().equals(ancestor)) {return true;}node = node.parent();}description.appendText("a Node which was not a descendant of ").appendValue(ancestor);return false;}@Overridepublic void describeTo(Description description) {description.appendText("a descendant Node of ").appendValue(ancestor);}public static IsDescendant descendantOf(Node ancestor) {return new IsDescendant(ancestor);} }測試類別:
public class IsDescendantTest {@Testpublic void should_pass_for_descendant_node() throws Exception {// GivenNode node = NodeTestFixture.nine;Node ancestor = NodeTestFixture.two;// ThenassertThat(node, is(descendantOf(ancestor)));}@Testpublic void should_fail_for_non_descendant_node() throws Exception {// GivenNode node = NodeTestFixture.ten;Node ancestor = NodeTestFixture.three;// ThenassertThat(node, is(not(descendantOf(ancestor))));} }ancestorOf(節(jié)點節(jié)點)
下一步將檢查給定的節(jié)點是否是輸入節(jié)點的祖先。 此操作實際上是descendantOf()的相反操作,因此我們將上移輸入節(jié)點的父級,而不是測試節(jié)點。
public class IsAncestor extends TypeSafeDiagnosingMatcher<Node> {private final Node descendant;public IsAncestor(Node descendant) {this.descendant = descendant;}@Overrideprotected boolean matchesSafely(Node node, Description description) {Node descendantCopy = descendant;while (descendantCopy.parent() != null) {if (descendantCopy.parent().equals(node)) {return true;}descendantCopy = descendantCopy.parent();}description.appendText("a Node which was not an ancestor of ").appendValue(descendant);return false;}@Overridepublic void describeTo(Description description) {description.appendText("an ancestor Node of ").appendValue(descendant);}public static IsAncestor ancestorOf(Node descendant) {return new IsAncestor(descendant);} }測試類別:
public class IsAncestorTest {@Testpublic void should_pass_for_ancestor_node() throws Exception {// GivenNode node = NodeTestFixture.two;Node descendant = NodeTestFixture.ten;// ThenassertThat(node, is(ancestorOf(descendant)));}@Testpublic void should_fail_for_non_ancestor_node() throws Exception {// GivenNode node = NodeTestFixture.three;Node descendant = NodeTestFixture.eight;// ThenassertThat(node, is(not(ancestorOf(descendant))));} }siblingOf(節(jié)點節(jié)點)
最后,我們將創(chuàng)建一個Matcher來檢查輸入節(jié)點是否是另一個節(jié)點的同級節(jié)點。 我們將檢查他們是否共享父母。 此外,當用戶嘗試測試根節(jié)點的同級節(jié)點時,我們將進行一些檢查并提供一些輸出。
public class IsSibling extends TypeSafeDiagnosingMatcher<Node> {private final Node sibling;public IsSibling(Node sibling) {this.sibling = sibling;}@Overrideprotected boolean matchesSafely(Node node, Description description) {if (sibling.parent() == null) {description.appendText("input root node cannot be tested for siblings");return false;}if (node.parent() != null && node.parent().equals(sibling.parent())) {return true;}if (node.parent() == null) {description.appendText("a root node with no siblings");}else {description.appendText("a node with parent ").appendValue(node.parent());}return false;}@Overridepublic void describeTo(Description description) {if (sibling.parent() == null) {description.appendText("a sibling of a root node");} else {description.appendText("a node with parent ").appendValue(sibling.parent());}}public static IsSibling siblingOf(Node sibling) {return new IsSibling(sibling);} }測試類別:
public class IsSiblingTest {@Testpublic void should_pass_for_sibling_node() throws Exception {// GivenNode a = NodeTestFixture.four;Node b = NodeTestFixture.five;// ThenassertThat(a, is(siblingOf(b)));}@Testpublic void should_fail_for_testing_root_node() throws Exception {// GivenNode a = NodeTestFixture.one;Node b = NodeTestFixture.six;// ThenassertThat(a, is(not(siblingOf(b))));}@Testpublic void should_fail_for_input_root_node() throws Exception {// GivenNode a = NodeTestFixture.five;Node b = NodeTestFixture.one;// ThenassertThat(a, is(not(siblingOf(b))));}@Testpublic void should_fail_for_non_sibling_node() throws Exception {// GivenNode a = NodeTestFixture.five;Node b = NodeTestFixture.six;// ThenassertThat(a, is(not(siblingOf(b))));} }5.結(jié)論
現(xiàn)在,我們已經(jīng)看到了如何創(chuàng)建Custom Hamcrest Matchers來測試既存的標準Java類和我們自己的類。 在下一個教程中,我們將把到目前為止所學到的所有知識放在一起,因為我們了解了將模擬和測試放在首位的軟件開發(fā)技術(shù)。 測試驅(qū)動開發(fā)。
翻譯自: https://www.javacodegeeks.com/2015/11/custom-hamcrest-matchers.html
總結(jié)
以上是生活随笔為你收集整理的定制Hamcrest Matchers的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: adf435编程_动态ADF列车:以编程
- 下一篇: maven配置testng_TestNG