Статьи

Hamcrest Содержащие Matchers

Документация Hamcrest 1.3 Javadoc для класса Matchers добавляет больше документации для нескольких методов этого класса, чем было доступно в Hamcrest 1.2. Например, четыре перегруженных метода метода содержат более описательную документацию Javadoc, как показано на двух снимках экрана сравнения, показанных ниже.

Хотя можно выяснить, как работают «содержащие» сопоставители, просто попробовав их, Javadoc в Hamcrest 1.3 облегчает понимание того, как они работают. Большинство разработчиков Java, вероятно, думают о поведении, подобном поведению String.contains (CharSequence) или Collection.contains (Object), когда они думают о методе contains() . Другими словами, большинство разработчиков Java, вероятно, думают о «содержит» как описание, если строка / коллекция содержит предоставленные символы / объекты среди других возможных символов / объектов. Тем не менее, для сопоставителей Hamcrest слово «содержит» имеет гораздо более конкретное значение. Поскольку документация Hamcrest 1.3 делает намного понятнее, сопоставления «содержит» гораздо более чувствительны к количеству элементов и порядку элементов, передаваемых этим методам.

Мои примеры, показанные здесь, используют JUnit и Hamcrest. Здесь важно подчеркнуть, что JAR-файл Hamcrest должен появляться в classpath модульных тестов до JAR-файла JUnit, иначе я должен использовать «специальный» JAR-файл JUnit, созданный для использования с автономным JAR Hamcrest. Использование любого из этих подходов позволяет избежать NoSuchMethodError и других ошибок (таких как ошибка org.hamcrest.Matcher.describeMismatch ), возникающих в результате несовпадения версий классов. Я написал об этом нюансе JUnit / Hamcrest в посте блога Moving Beyond Core Hamcrest в JUnit .

Следующие два снимка экрана показывают результаты (как показано в NetBeans 7.3) фрагментов кода модульного теста, которые я покажу позже в блоге, чтобы продемонстрировать Hamcrest, содержащий сопоставители. Предполагается, что в тестах есть несколько сбоев (7 тестов пройдено и 4 теста не пройдено), чтобы было ясно, где сопоставители Hamcrest могут работать не так, как ожидается, без чтения Javadoc. На первом изображении показано только 5 пройденных тестов, 2 теста не пройдены, и 4 теста вызвали ошибки Это потому, что у меня есть JUnit, перечисленный до Hamcrest на пути к классам Test Libraries проекта NetBeans. Второе изображение показывает ожидаемые результаты, потому что JAR Hamcrest появляется перед JAR JUnit в пути к классу проекта «Test Libaries».

В целях этой демонстрации у меня есть простой придуманный класс для тестирования. Исходный код этого класса Main показан ниже.

Main.java

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
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);
   }
}

Теперь, когда тестируемый класс показан, пришло время взглянуть на создание некоторых тестов на основе JUnit с использованием сопоставителей Hamcrest. В частности, тесты должны гарантировать, что строки, добавленные с помощью метода addString(String) класса, находятся в его базовом Set и доступны через метод getStrings() . Методы модульного тестирования, показанные далее, демонстрируют, как правильно использовать сопоставители Hamcrest, чтобы определить, содержатся ли добавленные строки в базовом Set класса

Использование Hamcrest содержит () Matcher с одной строкой в ​​множестве работ

01
02
03
04
05
06
07
08
09
10
11
12
/**
 * 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.
 */
@Test
public 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 содержит одинаковое количество элементов работает, если порядок соответствий

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
/**
 * 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.
 */
@Test
public 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.
 */
@Test
public 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() равно количеству строк в тестируемой коллекции. совпадение может работать, если проверенные элементы находятся в том же порядке, что и элементы в коллекции. С неупорядоченным Set этот порядок нельзя положиться, поэтому в contains() вряд ли можно использовать подходящее средство для юнит-теста в Set из нескольких элементов.

Использование Hamcrest содержит различное количество элементов, никогда не работает

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
/**
 * Demonstrate that contains will NOT pass when there is a different number
 * of elements asked about contains than in the collection.
 */
@Test
public 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.
 */
@Test
public 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’s MatrixInInnyOrder ()

Сопоставление containsInAnyOrder не так строго, как сопоставление contains() : оно позволяет проверять элементы в любом порядке в пределах содержащей коллекции.

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
/**
 * Test of addString and getStrings methods of class Main using Hamcrest
 * matcher containsInAnyOrder.
 */
@Test
public 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.
 */
@Test
public 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() все еще требует, чтобы все элементы содержащей коллекции были указаны для передачи. Следующий модульный тест не проходит, потому что это условие не выполняется.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
/**
 * 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.
 */
@Test
public 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, которые «работают» при работе со строками и коллекциями.

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
/**
 * Demonstrate hasItem() will also work for determining a collection contains
 * a particular item.
 */
@Test
public 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.
 */
@Test
public 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 () Matcher тестирует сдерживание с другого направления

Только что обсуждаемые hasItem() и hasItems() менее строги, чем contains() и даже менее строгие, чем containsInAnyOrder() и часто являются тем, что нужно, когда нужно просто убедиться, что один или несколько элементов находятся где-то в коллекции без озабоченность по поводу порядка элемента в этой коллекции или других возможных элементов в этой коллекции. Еще один способ использовать Hamcrest для определения того же отношения, но с противоположной точки зрения, это использовать isIn . isIn сопоставления isIn определяет, находится ли элемент где-то в коллекции, предоставленной для сопоставителя, без учета порядка этого элемента в коллекции или же в этой коллекции есть другие элементы.

01
02
03
04
05
06
07
08
09
10
11
12
13
/**
 * Use isIn matcher to test individual element is in provided collection.
 */
@Test
public 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 предоставляет богатый набор сопоставителей, которые можно использовать для определения того, находятся ли указанные элементы в указанной коллекции. Вот важные моменты, которые следует учитывать при принятии решения об их применении и определении того, что использовать:

  • Убедитесь, что JAR Hamcrest находится на пути к классам тестирования перед JUnit JAR.
  • Использовать contains когда вы хотите убедиться, что коллекция содержит все указанные элементы, а другие нет, и вы хотите, чтобы коллекция содержала элементы в указанном порядке.
    • Как правило, избегайте использования метода contains() с Set s, поскольку они неупорядочены по своей природе.
  • Используйте совпадение containsInAnyOrder когда вы все еще хотите строго проверить наличие в коллекции точно таких же элементов, как указано в тесте, но не заботитесь о порядке (применимо для Set ).
  • Используйте hasItem() и hasItems() чтобы запросить коллекцию, если она содержит, возможно, среди других незарегистрированных элементов и в произвольном порядке, указанный элемент или элементы.
  • Используйте isIn() сопоставления isIn() чтобы запросить, находится ли конкретный элемент в указанной коллекции, без учета того, находятся ли другие элементы в этой коллекции или в каком порядке этот элемент находится в содержащей коллекции.

Ссылка: Hamcrest Contents Matchers от нашего партнера JCG Дастина Маркса в блоге Inspired by Actual Events .