Pull to refresh
8
0
Send message
Очень жаль. :( Именно его статьи в тогда ещё печатном издании "][akep" в начале-середине 2000-х и позднее его книги были своего рода технологическим интернетом того времени по крайней мере для меня лично. Тогда я зачитывал статьи Криса Касперски в "][акере" и, по-моему, в "][акер-Спец" до дыр, хотя и многое тогда мне казалось сложным, но чертовски увлекательным и поражающим воображение. Я искренне продолжаю восхищаться этим Человеком, и хочу искренне сказать спасибо за всё то, что он успел сделать.

Честно говоря, нет, не рассматривал, поскольку ориентировался исключительно на чистую Retrolambda. Пример именно со Stream API (а не с таким "несерьёзным" Optional) был больше интересен как эксперимент, который показал бы некоторые нетривиальные техники портирования любой библиотеки или API, даже если для неё не существует бекпорта. Насколько я понимаю как работает streamsupport/streamsupport, она также требует привязки з пакету java8.*?

Мы, возможно, говорим о немного разных вещах. Я ставил целью статьи не показать преимущество тестирования поведения над тестированием состояния или наооборот, а в том, как с помощью средств языка и немножко — инструмента тестирования — создать некое формальное описание строгого порядка выполнения проверок в виде некого подобия DSL. Не более того. Это и повлекло за собой создание той "простыни", ведь без неё никак в обеих случаях. Т.е., упор именно на переходы между проверками:


  • сначала обязательная проверка на operation caller;
  • потом обязательная проверка на operation type;
  • и лишь потом — проверка аргументов value.

Единственное, что меня действительно огорчает — пришлось засыпать код лямбдами, потому что Mockito так работает. В случае использования только чистых JUnit/TestNG[+Hamcrest], в них (в лямбдах), конечно, не было бы нужды. И даже если я бы сделал упор на тестировании состояния, следуя вашей рекоммендации, у меня бы всё-равно в базовом абстрактном тесте был бы базовый метод, verifyLog(), который знал бы о состоянии, а производные тесты бы просто описывали конкретные правила, например:


verifyLog()
           .withOperationCaller(any(IAdministratorService.class))
           .withOperationType(eq(CREATE), eq(ADMINISTRATOR))
           .withValue(eq(VALUE_ADMINISTRATOR_NAME), eq(USERNAME))
           .withValue(eq(VALUE_FIRST_NAME), eq(FIRST_NAME))
           .withValue(eq(VALUE_LAST_NAME), eq(LAST_NAME))
           .withValue(eq(VALUE_EMAIL), eq(EMAIL))
           .then()
           ... // здесь не уверен

что по смыслу тождественно прямой проверке через has/contains или их аналоги, которые полностью инкапсулированы в базовом verifyLog().

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

Вот как раз этого я и пытаюсь избежать, поэтому предпочитаю тестирование поведения в таких случях. Мне бы не хотелось видеть состояние наружу только ради тестирования, даже если бы реализацей такого логгера стал бы внутренний класс, а сам интерфейс бы не регламентировал передачу внутреннего состояния наружу. В таком случае Mockito берёт всю возню с состоянием на себя и, я уверен, делает это великолепно.

Mockito утверждает, что это вообще-то антипаттерн

Да, это также упоминается в javadoc-е Mockito.verifyNoMoreInteractions(Object...). Но я считаю, что для систем, от которых ожидается чётко регламентированное поведение (как, например, строгое журналироване в тексте статьи), такая практика вполне даже применима и может быть использована именно во благо, а не во вред, о чём говорят разработчики Mockito, ссылаясь на возможное злоупотребление этой возможностью. Например, если метод тестируемого юнита обращается к какому-либо другому юниту и это влияет на состояние сообщения, которое планируется отправить в журнал — тест может указать на возможные проблемы во взаимодействии компонент между собой. И если это не считать проблемами, то тест может фактически выступать в роли документа, формально описывающего ожидаемые результаты взаимодействия нескольких компонент (если считать таковыми тесты, конечно). Намеренное игнорирование Mockito.verifyNoMoreInteractions(Object...) можно сравнить с намеренным подавлением предупреждений компилятора или других инструментов анализа.


Лучше тестировать состояние. В вашем примере можно сделать состояние логгера доступным для теста и проверять его с помощью assertThat.

Возможно, но мне такое утверждение кажется весьма спорным: здесь логгер выступает в роли компонента с write-only семантикой, и я бы не хотел видеть, как он предоставляет доступ к своему временному состоянию (имеется ввиду время жизни с begin() до log(...)) даже с помощью простейшего .contains(LogEntryKey key, Object value). Кроме того, тест с помощью Mockito позволяет гарантировать, что логгер собрал не только данные для следующей записи в журнале (т.е., состояние), а также и отослал эти данные куда-то (log(...)). Можно возразить, что и log(...) можно реализовать так, чтобы он выставлял некоторое состояние, описывающее факт "отосланного сообщения", но тогда наверняка пришлось бы пожертвовать или чистотой интерфейса, добавив в него что-то типа isLogged(), или в тестах завязываться на конкретную реализацию и каким-то образом узнавать о таком флажке (пусть даже приватном). Подход с Mockito, я считаю, более естественнен.


(И еще сильно режут глаза интерфейсы, начинающиеся с I — вы до этого на Delphi писали?)

Не полностью по Java, да. На самом деле это прямо позаимствовано из C#/.NET (я, честно говоря, с Delphi только TFoo помню). Мне кажется, это немного елегантнее, чем FooImpl, BarImpl и BazImpl, которые реализуют один и тот же интерфейс. Плюс, с практической точки зрения, такие имена удобнее читать за компьютерами коллег, которые принебрегают возможностями подстветки, или в системах, в которых такая возможность отсутствует вообще.

Не за что! На самом деле всё просто: в статье и так много декораторов, а так с помощью прокси получился дешёвый null object. И я, пожалуй, одолжу вашу предстоящую задачу и тоже решу её у себя.

Information

Rating
Does not participate
Registered
Activity