matchers依赖_定制Hamcrest Matchers
matchers依賴
本文是我們名為“ 用Mockito測試 ”的學院課程的一部分。
在本課程中,您將深入了解Mockito的魔力。 您將了解有關“模擬”,“間諜”和“部分模擬”的信息,以及它們相應的存根行為。 您還將看到使用測試雙打和對象匹配器進行驗證的過程。 最后,討論了使用Mockito的測試驅動開發(TDD),以了解該庫如何適合TDD的概念。 在這里查看 !
在本教程中,我們將使用Hamcrest API創建我們自己的自定義匹配器,以擴展Hamcrest提供的“開箱即用”功能。
目錄
1.為什么要使用自定義匹配器? 2.匹配器的解剖 3.現有課程的自定義匹配器1.為什么要使用自定義匹配器?
有時我們會遇到Hamcrest Matchers庫的限制。 我們需要在標準類(例如字符串,整數或列表)上具有新的匹配器功能,或者需要創建與已創建的高度自定義類匹配的匹配器。 在本教程中,我們將使用Hamcrest提供的工具針對這兩種情況創建匹配器。
2.匹配器的解剖
為了創建自定義匹配器,我們將擴展內置的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測試值時執行的方法。 如果是這種情況,它還負責報告Matcher為何不匹配。 不匹配描述是Hamcrest在失敗匹配器輸出的“但是:”部分之后使用的描述,因此不匹配描述應相應地設置格式。
第二種方法describeTo()用于生成匹配器正在檢查的內容的描述。 Hamcrest在失敗的匹配器的輸出的“ Expected:”部分之后使用此描述,因此該描述應相應設置格式。
在后臺,此Matcher將檢查輸入值是否不為null且類型正確,因此在執行命中我們的matchesSafely方法時,我們保證具有正確類型的值以進行匹配。
Hamcrest為我們完成了繁重的工作,因此這兩種方法都是實現自己的Hamcrest Matchers所需要的。 在接下來的示例中,我們還將添加一些語法糖,以簡化創建和使用自定義匹配器的過程。
3.現有課程的自定義匹配器
在本節中,我們將創建許多可用于現有類型的自定義匹配器。
甚至()
讓我們從創建匹配器開始,以確定輸入數字是否為偶數。 和以前一樣,我們將為整數擴展TypeSafeDiagnosingMatcher 。
public class IsEven extends TypeSafeDiagnosingMatcher<Integer> {@Overrideprotected boolean matchesSafely(Integer integer, Description description) {return false;}@Overridepublic void describeTo(Description description) {} }然后,我們將實現describeTo()方法,以提供對匹配器的期望描述:
@Override public void describeTo(Description description) {description.appendText("An Even number"); }我們可以使用description參數來創建Matcher的描述。 Description類提供了許多用于格式化輸入的輔助方法,在這種情況下,我們使用appendText()方法簡單地添加一些文本。
接下來,讓我們使用傳遞到matchesSafely方法的description參數來創建錯誤消息。 請記住,只有在故障情況下才能看到此輸出。 我們將在Description類中使用幾種方法來格式化消息:
@Override protected boolean matchesSafely(Integer integer, Description description) {description.appendText("was ").appendValue(integer).appendText(", which is an Odd number");return false; }接下來,我們將對數字進行實際檢查以查看其是否為偶數:
@Override protected boolean matchesSafely(Integer integer, Description description) {description.appendText("was ").appendValue(integer).appendText(", which is an Odd number");return integer % 2 == 0; }最后,我們將向類添加靜態工廠方法以使其易于在測試中使用:
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();} }現在,我們可以使用新的匹配器編寫一些測試。
我們創建一個名為IsEvenTest的新Test類,然后導入新的工廠方法:
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通常,在為匹配器編寫真實測試時,我們希望通過測試以確保邏輯是正確的,畢竟我們不希望在測試失敗的項目上工作! 如果我們想這樣做,我們可以將斷言更改為以下內容:
assertThat(test, not(isEven()));但是,在開發過程中編寫失敗的測試以手動檢查匹配器的輸出可能會很有用。
divisibleBy(整數除數)
現在我們已經看到了如何創建一個自定義Matcher來測試Integer的固有屬性。 是奇數還是偶數? 但是,我們在hamcrest Matcher庫中看到許多Matchers會根據輸入值進行測試。 現在,我們將自己創建一個Matcher。
想象一下,我們想定期測試數字以發現它們是否可以被另一個數字整除。 我們可以編寫一個自定義Matcher為我們完成此任務。
首先,像上一個示例一樣,創建一個無參匹配器,并將除數硬編碼為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,但是不匹配的描述稍微復雜一些。
我們如何從硬編碼值更改為輸入值? 沒什么花招,實際上就像添加一個私有成員變量并將其設置在構造函數中,然后將值通過factory方法傳遞一樣容易。
現在讓我們看一下完整的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);} }再次,讓我們創建一些測試來練習我們的新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>現在我們已經看到了如何為現有類創建自定義匹配器,這些類可以測試內部屬性或接受測試值。
接下來,我們將為自己編寫的類創建自定義匹配器。
4.針對您自己的班級的自定義匹配器
當我們使用自己創建的類進行工作時,自定義Hamcrest匹配器是測試庫中的強大工具。 現在,我們將創建一個域模型,并編寫一些自定義匹配器以使用該模型。
我們的模型:一棵樹
編程中常見的數據結構是由許多節點組成的樹。 這些節點可能只有一個父節點或一個或多個子節點。 沒有父節點的節點稱為根。 沒有子節點的節點稱為葉子。 如果可以通過X的父級從X到Y跟蹤路徑,則將節點X視為另一個節點Y的后代。如果Y是X的后代,則將節點X視為另一個節點Y的祖先。如果X和Y都共享父節點,則視為另一個節點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();} }請注意,對于這個簡單的示例,我們的類不是線程安全的。
該模型使我們能夠構建一個節點樹,從根節點開始并添加子節點。 我們可以在下面的小型應用程序類中看到這一點:
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);}} }這將產生以下輸出:
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=[]}現在我們已經定義了模型并知道如何使用它,我們可以開始針對它創建一些Matchers。
我們將在類中使用Node測試治具,以便針對一致的模型進行測試,該模型將與示例應用程序中定義的樹結構相同。 夾具在這里定義:
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);} }葉()
我們將創建的第一個Matcher將檢查輸入節點是否為葉節點。 它將通過檢查輸入節點是否有任何子節點來完成此操作,如果有則子節點不是葉節點。
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())));} }根()
現在,我們將創建一個匹配器,以檢查節點是否為根節點。 我們將通過檢查父節點的存在來做到這一點。
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())));} }DescendantOf(節點節點)
接下來是帶有輸入的匹配器,我們將檢查給定Node是否為輸入Node的后代。 我們將向上移動父母,看看是否在根之前到達了測試節點。
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(節點節點)
下一步將檢查給定的節點是否是輸入節點的祖先。 此操作實際上是descendantOf()的相反操作,因此我們將上移輸入節點的父級而不是測試節點。
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(節點節點)
最后,我們將創建一個Matcher來檢查輸入節點是否是另一個節點的同級節點。 我們將檢查他們是否共享父母。 此外,當用戶嘗試測試根節點的同級節點時,我們將進行一些檢查并提供一些輸出。
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.結論
現在我們已經看到了如何創建Custom Hamcrest Matchers來測試既有的標準Java類和我們自己的類。 在下一個教程中,我們將把到目前為止所學到的所有知識放在一起,因為我們了解了將模擬和測試放在首位的軟件開發技術。 測試驅動開發。
翻譯自: https://www.javacodegeeks.com/2015/11/custom-hamcrest-matchers.html
matchers依賴
總結
以上是生活随笔為你收集整理的matchers依赖_定制Hamcrest Matchers的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: apache lucene_Apache
- 下一篇: 地暖种类有哪些_地暖种类介绍