Как стать автором
Обновить

Комментарии 8

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

Ну и, конечно же, побеждаете.

Поздравляю вас — вы только что мастерски победили самого себя.
Я нигде ни с чем не борюсь (и уж тем более не побеждаю), я, если Вы обратили внимание, задаю вопросы и вызываю сообщество на дискуссию :)
Чем плотнее код, тем менее модульные(более интеграционные если хотите) должны быть тесты.

Безусловно это ухудшает качество тестирования, но позволяет сэкономить уйму времени.
Если у вас есть задача написать некое приложение(и вы примерно даже знаете как оно должно выглядеть в финале) — постарайтесь обойтись максимально простой архитектурой которая только возможна. И пишите тесты который отражают ваши требования и задачи.

Протестировать даже тот код что вы привели в примерах — можно. Тесты получатся не особо модульными, но что ж поделать.

Но вообще, размышляя о статье нужно иметь в виду что писал её разработчик высочайшего уровня. Возможно что его код не нуждается в подробном тестировании каждой мелочи. Ярые фанаты TDD могут прийти и начать кричать что я богохульствую, но это дело баланса. Можно удариться в нубскую крайность и тестировать абсолютно всё. А можно выйти на уровень выше(только если уверен что можешь) и тестировать то что считаешь нужным протестировать.
Мне кажется, что покрытие кода тестами имеет смысл не столько для начальной фазы жизни кода (хотя и тут оно имеет большой смысл, поскольку позволяет строить корректную систему по частям, а не отлаживать потом весь блоб целиком), сколько для последующей жизни кода. Придут другие разработчики. Код может меняться, код может рефакториться. Покрытие тестами даёт бóльшую свободу и бóльшее спокойствие при модификации или рефакторинге кода.
«Ведь всё, что сервер должен знать, это то, что данный класс может обработать любую команду, а не то, что он может обработать команду 1, команду 2 и команду 3.»
Тут ошибка.

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

Впрочем, главный вопрос состоит в следующем.

«Как вы предполагаете это тестировать?»
А что вы хотите тестировать, собственно? В данном коде можно тестировать два участка: правильный выбор обработчика команды и саму логику работы команды.

Начнем с конца: тестирование логики работы команды. Чем сценарий «вся логика в коде сервера» в этом отношении хуже, чем «вся логика в отдельных классах, по классу на команду»? Только тем, что ошибка в тесте может быть спровоцирована как неправильным выбором обработчика, так и неправильной логикой работы. А вот варианты «класс на команду» и «метод на команду» тестируются строго одинаково.

Теперь посмотрим на тестирование выбора обработчика. В случае «вся логика в коде сервера» этот сценарий протестировать нельзя. В случае «вся логика в одном методе одного обработчика» — тоже. А сценарии «класс на команду» и «метод на команду» тестируются опять-таки легко и одинаково.

Получается, что вы изначально пошли не в ту сторону: для получения более плотного кода не надо было сливать методы в один, надо было сливать классы — тогда вы получили бы одновременно легко тестируемый и при этом плотный код.

Но.

Надо помнить что код «выбор команды» — тривиален, и ошибки в нем (обычно) выявляются сразу при анализе теста. А это означает, что (а) не обязательно тестировать его отдельно (тесты все равно будут тривиальными) и (б) потеря времени на проверку выбора при проверке логики — минимальна. А это означает, что можно слить все в один метод, и с точки зрения тестируемости он будет вполне адекватен. Другое дело, что читается он отвратительно (как и любой switch со вложенной несвязанной логикой), и поэтому так делать все равно не надо.

Но.

На самом деле, оригинальная статья — не про «меньше методов». Статья про «меньше комментариев» и «меньше ненужных абстракций». Плотность кода повышается когда мы выбрасываем ненужные комментарии и промежуточные слои абстракции. В частности, сервер, создающий маршутизатор команд, где обработчик каждой команды добавляется динамически — это два лишних уровня абстракции (сервер не принимает решение, какую команду выполнить, маршрутизатор не знает, как именно выполнится команда). Понять при чтении такого кода «что именно выполнится, когда пришла команда Х» очень сложно — вплоть до невозможности.

Собственно, когда вы пишете «я бы смоделировал эту проблему» — вы уже идете против статьи, потому статья призывает меньше моделировать и больше реализовывать.
> А сценарии «класс на команду» и «метод на команду» тестируются опять-таки легко и одинаково.

Не согласен. В случае отдельного класса на команду Вы можете протестировать корректное мультиплексирования команд в различных вариациях в классе CompositeHandler, подделав конечные обработчики и описав ожидания — и это сделать будет просто, т.к. вы программируете в терминах интерфейсов. В случае метода на команду для такого рода проверок необходимо будет прибегнуть к грязным хакам типа порождения от тестируемого класса и замены нерелевантных методов no-op'ами.

> это два лишних уровня абстракции (сервер не принимает решение, какую команду выполнить, маршрутизатор не знает, как именно выполнится команда)

почему лишних? Разве сервер обязан знать, как конкретно выполнить команду? Мне кажется, задача сервера — общаться с клиентом, а также обрабатывать непредвиденные ситуации, возникающие при этом. В случае же спаивания всего этого в единую конструкцию мы получаем низкую связность (cohesion), не так ли?
«В случае отдельного класса на команду Вы можете протестировать корректное мультиплексирования команд в различных вариациях в классе CompositeHandler, подделав конечные обработчики и описав ожидания — и это сделать будет просто, т.к. вы программируете в терминах интерфейсов.»
Простите, я не понял, о чем вы. В вашей исходной задаче никакого мультиплексирования не было.

«В случае метода на команду для такого рода проверок необходимо будет прибегнуть к грязным хакам типа порождения от тестируемого класса и замены нерелевантных методов no-op'ами.»
Вообще-то, метод-на-команду тоже может быть в интерфейсе. После чего в нем радостно мокается один метод, а все остальные стабятся дефолтной реализацией.

«Разве сервер обязан знать, как конкретно выполнить команду?»
Зависит от того, что именно сервер делает, и какие команды (и насколько сложный протокол).

«почему лишних?»
Да потому что все это решается _одним_ уровнем — вбросом в сервер (пусть даже мы делим сервер, отвечающий за логику диалога с клиентом, и собственно выполняемый код) конкретной реализации интерфейса, выполняющего команды. Маршрутизатор и динамическая замена в нем команд — штука лишняя в условиях простой задачи.

Никогда не надо заранее переусложнять (а сложное надо решать все равно просто).
Простите, а что вы собралить модульно тестировать? Вы заменили абсолютно всё, что действительно важно на // perform logic x и предлагаете обсудить во что приятнее это завернуть. Да во что угодно, ничего полезного всё-равно делаться не будет. Такие вопросы без контекста (часто глубокого, изобилующего мелкими техническими деталями, ссылками на пункты ТЗ и баги в интегрируемом ПО) просто бессмысленны.

Возмите реальную проблему, решите её одним способом, но до конца, посчитайте сколько было багов и по каким причинам — такая статья будет иметь смысл. В ней будет _что_ обсуждать.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории