Статьи

Усовершенствованное создание Matcher Hamcrest

вступление

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

Typesafe Matchers

Возможно, вы заметили, что в методе match (), который мы разработали в прошлый раз, я добавил комментарий, в котором я использовал «условие yoda», чтобы избежать пустой проверки, а также проверки типа. Во-первых, не мешало бы сделать небольшое исследование условий yoda самостоятельно (я могу когда-нибудь выпустить статью об этом, но без гарантий), но самое важное, что следует отметить, это то, что какая-то проверка типов и необходима нулевая проверка. Это связано с тем, что метод match () принимает объект, а не тип, указанный в аргументе generics.

Как описано в документации Hamcrest:

Этот метод сопоставляется с Object, а не с универсальным типом T. Это связано с тем, что вызывающая программа Matcher не знает во время выполнения, что это за тип (из-за удаления типа с помощью универсальных Java-выражений).

Из-за этого нам нужно убедиться в типе передаваемого объекта. Кроме того, мы должны убедиться, что в него не передаются значения NULL (если наш конкретный Matcher не подходит для этого, но это очень редко), или в по крайней мере, убедитесь, что передача нулевого значения не вызовет исключение NullPointerException.

Но есть более простой способ: TypeSafeMatcher. Если вы расширите этот класс вместо класса BaseMatcher, он выполнит проверку типов и проверку на нулевое значение для вас, а затем передаст объект соответствующему методу, который принимает только тип, указанный в generics.

Определение TypeSafeMatcher очень похоже на определение Matcher, как мы это делали в прошлый раз, с некоторыми отличиями: вместо переопределения match () вы переопределяете matchSafely (), который принимает универсальный тип вместо Object; и вместо переопределения описываетMismatch (), вы переопределяете descriptionMismatchSafely (). Может быть сюрпризом, что не существует нового descriptionTo (), но, поскольку в нем нет ничего, кроме Description, нет необходимости в безопасной версии.

В противном случае создание TypeSafeMatcher будет практически таким же.

Я должен упомянуть кое-что, что я забыл на прошлой неделе. Кому-то, кто определяет свои собственные Matchers, не нужно переопределять методы descriptionMismatch () или descriptionMismatchSafely (). BaseMatcher и TypeSafeMatcher имеют реализации по умолчанию тех методов, которые просто выводят «was item.toString () » (или «was itemClassName ( item.toString () )», если TypeSafeMatcher получает элемент неправильного типа).

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

Примечание о других расширяемых классах Matcher

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

Во-первых, это CustomMatcher и CustomTypeSafeMatcher. Они предназначены для создания одноразовых совпадений с помощью анонимных классов. Они могут быть полезны, но я бы предпочел всегда делать правильную реализацию на тот случай, если она мне когда-нибудь понадобится.

Затем есть DiagnosingMatcher и TypeSafeDiagnosingMatcher, в которых вы создаете описание несоответствия в методе match (). Казалось бы, это хороший способ убить двух зайцев одним выстрелом, но у меня есть несколько недостатков: 1) он нарушает SRP 2) если есть несоответствие, он делает второй вызов метода match () просто для заполнения в описании несоответствия. Таким образом, первый вызов игнорирует получение описания, а второй игнорирует сопоставление.

Последний специальный Matcher, который вы можете расширить, это FeatureMatcher. Это может быть довольно полезно, но это сложно понять (я не уверен, правильно ли я это понимаю — пока я не попробую сделать что-то свое или читать, как это сделать). Если я пойму это и получу хорошее понимание, я напишу здесь еще один пост.

Сопоставители без гражданства

Любой Matcher, который не требует ничего переданного в его конструктор (и, следовательно, это статический метод фабрики), является Matcher без состояния. Они имеют небольшое преимущество перед другими Matchers в том, что вам нужен только один его экземпляр, чтобы существовать в любой точке, который можно использовать повторно в любое время, когда вам нужно использовать этот Matcher.

Это действительно простое дополнение. Все, что вам нужно сделать, это создать статический экземпляр класса и заставить ваши статические фабрики возвращать этот экземпляр вместо вызова конструктора. IsEmptyString Matcher, который фактически поставляется с библиотекой, делает это (наш пример в прошлый раз не сделал, но это было для простоты).

Сокращение количества статических импортов

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

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

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
import org.hamcrest.core.IsEqual;
import org.hamcrest.core.IsNull;
import org.hamcrest.core.IsSame;
import org.hamcrest.Matcher;
 
public class CoreMatchers
{
   public static  Matcher equalTo(T object) {
      return IsEqual.equalTo(object);
   }
 
   public static Matcher notNullValue() {
      return IsNull.notNullValue();
   }
 
   public static  Matcher notNullValue(Class type) {
      return IsNull.notNullValue(type);
   }
 
   public static Matcher nullValue() {
      return IsNull.nullValue();
   }
 
   public static  Matcher nullValue(Class type) {
      return IsNull.nullValue(type);
   }
 
   public static  Matcher sameInstance(T target) {
      return IsSame.sameInstance(target);
   }
 
   public static  Matcher theInstance(T target) {
      return IsSame.theInstance(target);
   }
}

Затем, чтобы использовать любой или все эти Matchers, вам нужно только выполнить статический импорт CoreMatchers. * Существует также способ генерации этих комбинированных классов Matcher, как показано в официальных руководствах по Hamcrest . Я не буду это обсуждать, поскольку это выходит за рамки этой статьи, и я не фанат этого.

Заключительные советы: Нейминг

Если вы ознакомитесь с официальным руководством Hamcrest и / или посмотрите встроенные Matchers, вы можете заметить тенденцию именования статических фабричных методов. Общая грамматика соответствует «утверждают, что testObject — это factoryMethod ». Грамматика имени метода обычно предназначена для действия в настоящем времени, которому может предшествовать «is». Когда вы называете свои собственные статические фабричные методы, вы обычно должны следовать этому соглашению, но я на самом деле предлагаю вставить «is» в имя уже. Таким образом, пользователям вашего Matcher не нужно вкладывать ваш метод в метод is (). Однако, если вы сделаете это, вам нужно будет создать и обратную функцию. Причина, по которой метод is () позволяет обернуть ваш Matcher, заключается в том, что вы можете также обернуть его в метод not (), чтобы проверить обратное тому, что вы уже тестировали. Это приводит к такому предложению, как «утверждать, что testObject не является factoryMethod ». Если вы чувствуете, что следование соглашению слишком ограничительно для вашего конкретного Matcher, просто убедитесь, что вы используете настоящий тест с напряженным действием. Например, я создал средство сопоставления, которое проверяет, не было ли выброшено исключение, и чей статический фабричный метод — throwsA (). Мне просто не нравилось называть это throwingA (), чтобы работать с «is». Но, опять же, если вы нарушаете соглашение, вы должны быть уверены, что создали метод обратной статической фабрики; dontThrowA (), например. Если вы реализуете свои собственные обратные фабрики, самый простой способ сделать это, как правило, заключить вашу положительную фабрику в not (). Итак, мой метод doesntThrowA () не вернется (throwsA ()). Однако будьте осторожны: простое изменение истинного и ложного иногда не дает правильного обратного.

Outro

Ну, это все, что у меня есть для тебя. Если есть что-то еще о Hamcrest Matchers, о котором вы хотите, чтобы я рассказал, сообщите мне в комментариях. В противном случае, вы можете провести свое собственное исследование о Matcres Hamcrest на его странице github. На следующей неделе я расскажу, как вы можете заставить своих Matcher Hamcrest проверять несколько вещей так же бегло, как AssertJ делает их утверждения.

Ссылка: Расширенное создание Hamcrest Matchers от нашего партнера JCG Джейкоба Циммермана в блоге « Идеи программирования с Джейком» .