У статических методов есть одна мощная, но и в то же время весьма нежелательная особенность: их можно вызвать из любого места в коде, не особо имея возможность регламентировать порядок их вызова. Зачастую такой контроль очень важен, но иногда порядок не имеет очень большого смысла. Например, осуществлять проверки в юнит-тестах часто можно не в очень строгом порядке. И чтобы гарантировать, что в тестируемом юните выполенены все проверки, в Mockito существует всё тот же статический метод verifyNoMoreInteractions(...). Иногда можно по ошибке вызвать такой метод ещё до последнего verify(...) и потом с огорчением наблюдать "красный" тест. Но что если переложить заботу о порядке выполнения проверок на сам компилятор? Предположим, что некий тестируемый модуль располагает следующим тестом: Догадаться, что именно проверяет этот тест несложно: он проверяет, вызвал ли тестируемый метод юнита все важные методы структурированного логгера. Поскольку в конце теста есть вызывается ещё и verifyNoMockInteractions(...), гарантируется, что у мока не осталось методов, для которых не было написано проверок. Кстати, интерфейс структурированного логгера предельно прост, но я приведу его здесь в несколько урезанном виде, потому как код взят из реального проекта. Как было сказано выше, статические методы, которыми усеян тест, не гарантируют, что проверки всех типов будут осуществлены. И, наверняка, такой тест завершится с ошибкой. Под типами проверок в этом тесте я подразумеваю возможность определить: По сути, имеется конечный набор требований, который бы можно было заставить выполнится в определённом порядке.
Для решения этой задачи можно рассмотреть варианты с использованием шаблона стратегия, где бы был некий интерфейс с методами на каждый тип проверки, и каждый метод отвечал бы за свой аспект журналирования. Или, например, шаблонный метод. Но очевидно, что такие подходы были бы весьма громоздки, ненадёжны в плане гарантирования разделения аспектов по именно соответсвующим им методам. Да и читабельностью бы пришлось пожертвовать, чего уж точно не хотелось бы делать. Лет пять назад, помнится, я наткнулся в Интернете на статью, описывающей реализацию шаблона строитель, которая, используя некоторые не совсем очевидные техники, гарантировала, что создание сложного объекта будет осуществлено в правильном порядке. Имеется ввиду следующее: для некоего объекта-строителя сначала можно вызвать только метод setFoo(), и лишь потом — setBar() с последующим build(). И никак не в другом порядке, ведь за порядком следит компилятор! Похожий подход, но уже с другой реализацией, можно использовать и для упрощённого написания тестов по правилам, описанным выше строго в одном порядке, при этом без использования шаблонного метода. Учитывая кое-как формализированные особенности тестирования в этом случае, можно создать набор таких интерфейсов, которые и будут заниматься сцеплением таких переходов. И для удобства можно использовать текучий интерфейс, который позволит выстроить элегантную цепочку проверок. Почти все интерфейсы получилось проаннотировать как @FunctionalInterface, хотя это и не является необходимостью. Тем не менее, "вариадический" интерфейс обладает двумя методами, поскольку нужно как-то сообщить о завершении проверки журналирования аргументов операции. Итак, оригинальный код теста теперь может принять следующий вид: Ну и, собственно, само упрощение, ради которого было усложнено базовый функционал тестов: Как по мне, код стал надёжнее и весьма красивее. Да и удобнее тоже. И главное — любая умная IDE при нажатии на точку сама подсказывает, каким должен быть следующий шаг. Таким образом, и компилятор и IDE добавляют ешё немного уверенности в том, насколько хорошо написан тест. Кстати, почему используются Supplier-ы и лямбда-выражения? Дело в том, что Mockito проверяет, передаются ли стабы напрямую в моки, и если нет — бросает исключение. На самом деле, здесь, насколько мне известно, правила немного сложнее, и, например, Mockito игнорирует анонимные классы. И ввиду этого факта есть небольшая лазейка: Mockito не отслеживает передачу матчеров через return, что открывает дорогу к использованию лямбд. Это немного усложняет код и читабельность, но лямбды достаточно неплохо с этим справляются. В итоге получился следующий результат: