Статьи

Изготовленные на заказ Hamcrest Matchers

Эта статья является частью нашего курса Академии под названием « Тестирование с помощью Mockito» .

В этом курсе вы погрузитесь в магию мокито. Вы узнаете о издевательствах, шпионах и частичных издевательствах, а также их соответствующем поведении. Вы также увидите процесс проверки с помощью тестовых двойников и сопоставителей объектов. Наконец, обсуждается разработка через тестирование (TDD) с Mockito, чтобы увидеть, как эта библиотека вписывается в концепцию TDD. Проверьте это здесь !

В этом руководстве мы будем использовать API-интерфейс Hamcrest для создания собственных пользовательских сопоставителей, чтобы расширить функциональность «из коробки», предоставляемую Hamcrest.

1. Почему Custom Matchers?

Время от времени мы будем сталкиваться с пределами библиотеки Хамкреста Matchers. Либо нам нужны новые функции сопоставления в стандартных классах, таких как строки, целые числа или списки, либо нам нужно создавать сопоставители, которые соответствуют специально настроенным классам, которые мы создали. В этом уроке мы создадим сопоставители для обоих обстоятельств, используя инструменты, которые предоставляет Hamcrest.

2. Анатомия Matcher

Для создания наших пользовательских сопоставителей мы будем расширять встроенный абстрактный класс TypeSafeDiagnosingMatcher . Если вы расширите этот класс в вашей IDE для типа Integer, вы увидите два абстрактных метода из этого класса:

01
02
03
04
05
06
07
08
09
10
11
public class BlankMatcher extends TypeSafeDiagnosingMatcher<Integer> {
   @Override
   protected boolean matchesSafely(Integer integer, Description description) {
       return false;
   }
 
   @Override
   public void describeTo(Description description) {
 
   }
}

Первый метод matchesSafely() — это то место, куда идет мясо нашего Matcher, это метод, который выполняется Hamcrest, когда мы хотим использовать наш Matcher для проверки значения. Он также отвечает за сообщение о том, почему Matcher не соответствует, если это так. Описание несоответствия — это то, что используется Hamcrest после раздела «Но:» его вывода для несоответствующего сопоставления, и поэтому описание несоответствия должно быть соответствующим образом отформатировано.

Второй метод descriptionTo describeTo() используется для генерации описания того, что проверяет сопоставитель. Это описание используется Hamcrest после раздела «Ожидается:» его вывода для несоответствующего сопоставителя, поэтому описание должно быть соответствующим образом отформатировано.

Изнутри этот Matcher проверит, что входное значение не равно нулю и имеет правильный тип, поэтому мы обязательно получим значение правильного типа для сопоставления к тому времени, когда выполнение достигнет нашего метода matchesSafely .

Hamcrest делает тяжелую работу за нас, поэтому эти два метода — все, что нам нужно для реализации наших собственных Matcher Hamcrest. В следующих примерах мы также добавим синтаксический сахар, чтобы упростить создание и использование наших пользовательских сопоставителей.

3. Пользовательские совпадения для существующих классов

В этом разделе мы создадим несколько пользовательских сопоставлений, которые работают с существующими типами.

3.1. даже()

Давайте начнем с создания соответствия, чтобы определить, является ли входное число четным числом. Как и прежде, мы расширим TypeSafeDiagnosingMatcher для Integer.

01
02
03
04
05
06
07
08
09
10
11
public class IsEven extends TypeSafeDiagnosingMatcher<Integer> {
   @Override
   protected boolean matchesSafely(Integer integer, Description description) {
       return false;
   }
 
   @Override
   public void describeTo(Description description) {
 
   }
}

Затем мы реализуем метод descriptionTo describeTo() чтобы предоставить описание того, что мы ожидаем от сопоставителя:

1
2
3
4
@Override
public void describeTo(Description description) {
   description.appendText("An Even number");
}

Мы можем использовать параметр description чтобы создать описание нашего Matcher. Класс Description предоставляет несколько вспомогательных методов для форматирования ввода, и в этом случае мы используем метод appendText() для простого добавления текста.

Далее давайте создадим наше сообщение об ошибке, используя параметр description который передается методу matchesSafely . Помните, что этот вывод будет виден только в состоянии сбоя. Мы будем использовать несколько методов в классе Description для форматирования нашего сообщения:

1
2
3
4
5
@Override
protected boolean matchesSafely(Integer integer, Description description) {
   description.appendText("was ").appendValue(integer).appendText(", which is an Odd number");
   return false;
}

Далее мы осуществим фактическую проверку числа, чтобы увидеть, является ли оно четным или нет:

1
2
3
4
5
@Override
protected boolean matchesSafely(Integer integer, Description description) {
   description.appendText("was ").appendValue(integer).appendText(", which is an Odd number");
   return integer % 2 == 0;
}

Наконец, мы добавим в класс статический метод фабрики, чтобы его было легко использовать в тестах:

1
2
3
public static IsEven isEven() {
   return new IsEven();
}

Собрав все вместе, мы имеем следующий класс Matcher:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public class IsEven extends TypeSafeDiagnosingMatcher<Integer> {
   @Override
   protected boolean matchesSafely(Integer integer, Description description) {
       description.appendText("was ").appendValue(integer).appendText(", which is an Odd number");
       return integer % 2 == 0;
   }
 
   @Override
   public void describeTo(Description description) {
       description.appendText("An Even number");
   }
 
   public static IsEven isEven() {
       return new IsEven();
   }
}

Теперь мы можем написать несколько тестов, используя наш новый механизм сопоставления.

Мы создаем новый класс Test с именем IsEvenTest и затем импортируем наш новый метод фабрики:

1
2
3
4
5
import static com.javacodegeeks.hughwphamill.mockito.hamcrest.matchers.IsEven.isEven;
 
public class IsEvenTest {
 
}

Далее мы напишем тестовый метод, чтобы проверить положительный случай, когда совпадение оценивается как истинное.

1
2
3
4
5
6
7
8
@Test
public void should_pass_for_even_number() throws Exception {
   // Given
   Integer test = 4;
 
   // Then
   assertThat(test, isEven());
}

И метод, чтобы показать выходные данные, где совпадение не соответствует.

1
2
3
4
5
6
7
8
@Test
public void should_fail_for_odd_number() throws Exception {
   // Given
   Integer test = 5;
 
   // Then
   assertThat(test, isEven());
}

Который будет генерировать следующий вывод:

1
2
3
java.lang.AssertionError:
Expected: An Even number
     but: was <5>, which is an Odd number

Обычно при написании реальных тестов для сопоставителей мы хотим пройти тестирование, чтобы убедиться в правильности логики, в конце концов, мы не хотим работать над проектами с ошибочными тестами! Если бы мы хотели сделать это, мы могли бы изменить утверждение на что-то вроде следующего:

1
assertThat(test, not(isEven()));

Однако может быть полезно написать провальные тесты во время разработки, чтобы вручную проверить выходные данные сопоставителя.

3.2. divisibleBy (целочисленный делитель)

Теперь мы увидели, как создать собственный Matcher для проверки встроенного свойства Integer; это странно или нечетно? Однако мы видим много Matchers в библиотеке Hamcrest Matcher, которые проверяют входное значение. Теперь мы создадим такой Matcher сами.

Представьте, что мы хотим регулярно проверять числа, чтобы определить, делятся ли они на другое число. Мы можем написать собственный Matcher, чтобы сделать это для нас.

Давайте начнем с создания Matcher без аргументов, как в нашем последнем примере, и жестко закодируйте делитель на 3.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
public class DivisibleBy extends TypeSafeDiagnosingMatcher<Integer> {
 
   @Override
   protected 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;
   }
 
   @Override
   public 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, но с немного более сложным описанием несоответствия.

Как мы можем перейти от нашего жестко закодированного значения к входному значению? В этом нет ничего хитрого, на самом деле это так же просто, как добавить частную переменную-член и установить ее в конструкторе, а затем передать значение через метод фабрики.

Давайте посмотрим на полный Matcher сейчас:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class DivisibleBy extends TypeSafeDiagnosingMatcher<Integer> {
   
   private final Integer divisor;
 
   public DivisibleBy(Integer divisor) {
       this.divisor = divisor;
   }
 
   @Override
   protected 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;
   }
 
   @Override
   public 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:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
public class DivisibleByTest {
 
   @Test
   public void should_pass_for_true_divisor() throws Exception {
       // Given
       Integer test = 15;
 
       // Then
       assertThat(test, is(divisibleBy(5)));
   }
 
   @Test
   public void should_fail_for_non_divisor() throws Exception {
       // Given
       Integer test = 17;
 
       // Then
       assertThat(test, is(divisibleBy(3)));
   }
}

Результат неудачного теста

1
2
3
java.lang.AssertionError:
Expected: is A number divisible by 3
     but: was <17> which left a remainder of <2>

Теперь мы увидели, как создавать пользовательские сопоставления для существующих классов, которые могут либо проверять внутренние свойства, либо принимать тестовые значения.

Далее мы создадим Custom Matchers для классов, которые мы написали сами.

4. Пользовательские Matchers для ваших собственных классов

Специальные тестеры Hamcrest являются мощным инструментом в нашем тестовом арсенале, когда мы работаем с классами, которые создали сами. Теперь мы создадим модель предметной области и напишем несколько пользовательских сопоставителей для работы с этой моделью.

4.1. Наша модель: дерево

Общая структура данных в программировании — это дерево, состоящее из множества узлов. Эти узлы могут иметь одного родителя или одного или нескольких дочерних узлов. Узел, у которого нет родителей, называется корневым. Узел, у которого нет потомков, называется листом. Узел X считается потомком другого узла Y, если путь можно проследить от X к Y через родительский элемент X. Узел X считается предком другого узла Y, если Y является потомком X. Узел X является считается братом другого узла Y, если оба X и Y имеют общего родителя.

Мы будем использовать наш узел для хранения единственного значения типа int.

Наша модель будет иметь единственный класс с именем Node, который выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/**
* 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();
   }
}

Обратите внимание, что для этого упрощенного примера наш класс не является потокобезопасным.

Эта модель позволяет нам строить дерево узлов, начиная с корневого узла и добавляя потомков. Мы можем увидеть это в действии в следующем небольшом классе приложения:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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);
       }
 
   }
}

Который будет производить следующий вывод:

01
02
03
04
05
06
07
08
09
10
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 в наших классах, чтобы протестировать согласованную модель, которая будет иметь ту же структуру дерева, которую мы определили в нашем примере приложения. Приспособление определено здесь:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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);
   }
}

4.2. лист ()

Первый Matcher, который мы создадим, проверит, является ли входной узел листовым узлом. Это будет достигнуто путем проверки, имеет ли входной узел дочерние элементы, если это так, то это не конечный узел.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
public class IsLeaf extends TypeSafeDiagnosingMatcher<Node> {
   @Override
   protected 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;
   }
 
   @Override
   public void describeTo(Description description) {
       description.appendText("a leaf node with no children");
   }
 
   public static IsLeaf leaf() {
       return new IsLeaf();
   }
}

Тестовый класс:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
public class IsLeafTest extends NodeTestFixture {
 
   @Test
   public void should_pass_for_leaf_node() throws Exception {
       // Given
       Node node = NodeTestFixture.seven;
 
       // Then
       assertThat(node, is(leaf()));
   }
 
   @Test
   public void should_fail_for_non_leaf_node() throws Exception {
       // Given
       Node node = NodeTestFixture.four;
 
       // Then
       assertThat(node, is(not(leaf())));
   }
}

4,3. Корень ()

Теперь мы создадим средство сопоставления, чтобы проверить, является ли узел корневым узлом. Мы сделаем это, проверив наличие родительского узла.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
public class IsRoot extends TypeSafeDiagnosingMatcher<Node> {
   @Override
   protected boolean matchesSafely(Node node, Description mismatchDescription) {
       if (node.parent() != null) {
           mismatchDescription.appendText("a node with parent ")
                   .appendValue(node.parent());
           return false;
       }
       return true;
   }
 
   @Override
   public void describeTo(Description description) {
       description.appendText("a root node with no parent");
   }
 
   public static IsRoot root() {
       return new IsRoot();
   }
}

Тестовый класс:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
public class IsRootTest {
 
   @Test
   public void should_pass_for_root_node() throws Exception {
       // Given
       Node node = NodeTestFixture.one;
 
       // Then
       assertThat(node, is(root()));
   }
 
   @Test
   public void should_fail_for_non_root_node() throws Exception {
       // Given
       Node node = NodeTestFixture.five;
 
       // Then
       assertThat(node, is(not(root())));
   }
}

4.4. downndantOf (узел узла)

Далее идет сопоставление со входом, мы проверим, является ли данный узел потомком входного узла. Мы пройдем через родителей, чтобы посмотреть, не попадем ли мы в тестовый узел перед корнем.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class IsDescendant extends TypeSafeDiagnosingMatcher<Node> {
 
   private final Node ancestor;
 
   public IsDescendant(Node ancestor) {
       this.ancestor = ancestor;
   }
 
   @Override
   protected 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;
   }
 
   @Override
   public void describeTo(Description description) {
       description.appendText("a descendant Node of ").appendValue(ancestor);
   }
 
   public static IsDescendant descendantOf(Node ancestor) {
       return new IsDescendant(ancestor);
   }
}

Тестовый класс:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
public class IsDescendantTest {
 
   @Test
   public void should_pass_for_descendant_node() throws Exception {
       // Given
       Node node = NodeTestFixture.nine;
       Node ancestor = NodeTestFixture.two;
 
       // Then
       assertThat(node, is(descendantOf(ancestor)));
   }
 
   @Test
   public void should_fail_for_non_descendant_node() throws Exception {
       // Given
       Node node = NodeTestFixture.ten;
       Node ancestor = NodeTestFixture.three;
 
       // Then
       assertThat(node, is(not(descendantOf(ancestor))));
   }
}

4,5. ancestorOf (узел узла)

Далее проверит, является ли данный узел предком входного узла. Эта операция, в сущности, является противоположностью функции descendantOf() поэтому мы будем перемещать родительские элементы входного узла вместо тестового узла.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class IsAncestor extends TypeSafeDiagnosingMatcher<Node> {
 
   private final Node descendant;
 
   public IsAncestor(Node descendant) {
       this.descendant = descendant;
   }
 
   @Override
   protected 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;
   }
 
   @Override
   public void describeTo(Description description) {
       description.appendText("an ancestor Node of ").appendValue(descendant);
   }
 
   public static IsAncestor ancestorOf(Node descendant) {
       return new IsAncestor(descendant);
   }
}

Тестовый класс:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
public class IsAncestorTest {
   @Test
   public void should_pass_for_ancestor_node() throws Exception {
       // Given
       Node node = NodeTestFixture.two;
       Node descendant = NodeTestFixture.ten;
 
       // Then
       assertThat(node, is(ancestorOf(descendant)));
   }
 
   @Test
   public void should_fail_for_non_ancestor_node() throws Exception {
       // Given
       Node node = NodeTestFixture.three;
       Node descendant = NodeTestFixture.eight;
 
       // Then
       assertThat(node, is(not(ancestorOf(descendant))));
   }
}

4,6. siblingOf (узел узла)

Наконец, мы создадим Matcher, чтобы проверить, является ли входной узел одним из других узлов. Мы проверим, разделяют ли они родителя. Кроме того, мы сделаем несколько проверок и предоставим некоторые выходные данные, когда пользователь пытается проверить корневые узлы на наличие элементов одного уровня.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class IsSibling extends TypeSafeDiagnosingMatcher<Node> {
 
   private final Node sibling;
 
   public IsSibling(Node sibling) {
       this.sibling = sibling;
   }
 
   @Override
   protected 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;
   }
 
   @Override
   public 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);
   }
}

Тестовый класс:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class IsSiblingTest {
 
   @Test
   public void should_pass_for_sibling_node() throws Exception {
       // Given
       Node a = NodeTestFixture.four;
       Node b = NodeTestFixture.five;
 
       // Then
       assertThat(a, is(siblingOf(b)));
   }
 
   @Test
   public void should_fail_for_testing_root_node() throws Exception {
       // Given
       Node a = NodeTestFixture.one;
       Node b = NodeTestFixture.six;
 
       // Then
       assertThat(a, is(not(siblingOf(b))));
   }
 
   @Test
   public void should_fail_for_input_root_node() throws Exception {
       // Given
       Node a = NodeTestFixture.five;
       Node b = NodeTestFixture.one;
 
       // Then
       assertThat(a, is(not(siblingOf(b))));
   }
 
   @Test
   public void should_fail_for_non_sibling_node() throws Exception {
       // Given
       Node a = NodeTestFixture.five;
       Node b = NodeTestFixture.six;
 
       // Then
       assertThat(a, is(not(siblingOf(b))));
   }
}

5. Заключение

Теперь мы увидели, как создавать пользовательские сопоставители Hamcrest для тестирования как ранее существующих стандартных классов Java, так и наших собственных классов. В следующем уроке мы объединим все, что мы узнали, так как мы узнаем о технике разработки программного обеспечения, которая ставит Mocking и Testing во главу угла; Разработка через тестирование.