Тестирование параметров

DESC_2011InviteCoverВ статье о тестировании поведения было написано буквально пару слов о сравнении фактических аргументов методов с ожидаемыми. Пришло время рассказать о этом механизме подробнее.

Stub mocks и pattern matching

Не совсем корректно называть то, о чем я сейчас напишу «pattern matching», но идея крайне близка.

Возьмём код из статьи о тестировании поведения и дополним интерфейс UserRepository:

Посмотрим, что будет, если мы определим разные варианты ответов для разных логинов:

смотрит, с каким аргументом вызван метод и возвращает ответ, который был задан для этого аргумента. Это удобно, поскольку позволяет создавать наборы «параметр-результат» на все случаи жизни. C другой стороны, если результат всех вызовов один, а параметров много, это неудобно, поскольку требует создания таких наборов для каждого параметра.

Argument matchers to the rescue

Аргументы можно проверять не только непосредственным заданием значения аргумента, но и описательно,  с использованием матчеров. И это не hamcrest матчеры, хотя они очень похожи.

В случае с проблемой, описанной выше, можно использовать матчер anyString():

anyString() делает очевидную вещь — разрешает объекту принимать в качестве параметра любую строку. Кроме anyString() существуют any*() матчеры для всех встроенных типов. Для матчера  anyObject() можно дополнительно задать класс этого самого any object. Альтернативой anyObject(Class clazz) будет матчер isA(Class clazz).

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

При этом, смешивать в одном вызове матчеры и непосредственно заданные параметры нельзя. Хотя если очень хочется, то можно, используя матчер  eq()

Кроме eq(), сранивающего объекты через вызов equals(), существуют и другие сравнивающие значения матчеры:

  • eq() для чисел с плавающей запятой, позволяющий задать погрешность
  • eqAry() для массивов. Массивы сравниваются через Arrays.equals()
  • lt(),leq() операции «меньше» и «меньше или равно». Работают для всех численных типов и типов, реализующих Comparable
  • ge(),geq() соответственно «больше» и «больше или равно». Ограничения те же, что и для le()/leq()
  • cmpEq() сравнивает объекты вызовом Comparable.compareTo(). Ограничения те же.
  • same() почти eq(), но сравнивает идентичность объектов, а не их эквивалентность.

Ещё стоит упомянуть isNull()/notNull(). Из их названия очевидно, что они проверяют 🙂

Матчеры можно группировать друг с другом:

or() срабатывает, если по крайней мере один матчер срабатывает. Парный ему матчер  and() требует срабатывания обоих матчеров. К двум логическим операциям прилагается матчер not(), который инвертирует действие своего аргумента.

В коде, показывающем как группировать матчеры, я продемонстрировал и два новых матчера, специфичных для строк: startsWith() проверяет, начинается ли аргумент с заданной подстроки; matches() (или find()) принимает регулярное выражение и накладывает его на аргумент.

Код примера доступен на github