Как стать автором
Обновить
-17
0
Антон Нехаев @nehaev

Пользователь

Отправить сообщение
Да, есть такая проблема. В Scala ее пытаются решить с помощью макросов: github.com/heathermiller/spores
Всегода недоумевал, почему так происходит в блокноте. Теперь наконец все стало на свои места. Спасибо за такое подробное расследование!
> Сериализация лямбда-функций весьма представляет собой весьма интересный, но практически бесполезный механизм.

Как ни странно, но сериализация лямбд вполне успешно и с большой пользой применяется на практике. Например, на этом основана распределенная обработка данных в Apache Spark (см. spark.apache.org/docs/latest/programming-guide.html#passing-functions-to-spark).
> вынос генерации в отдельную функцию — такой же искусственный интерфейс

Нет. Исходя из наличия сложной логики генерации, выделение этой логики как отдельной функции — это вполне естественный шаг рефакторинга. А вот специальный интерфейс, который нужен только для тестирования — это искусственная конструкция.

> Не объявить, что оно тривиально, а проверить его.

Зачем проверять тривиальные вещи, если можно этого не делать и потратить высвободившееся время с реальной, измеряемой пользой?

> посмотрите на пример моего кода, то вы поймете, почему ничего гарантированного в нем нет

Я уже прокомментировал этот пример. Если прорефакторить его в соответствии с предложенным мною (в комментарии выше) подходом, то ничего не меняется. Усложнившаяся генерация сообщений по-прежнему тестируется юнит-тестом без моков, отправка сообщений по-прежнему тривиальна и не требует тестирования. (Понимаю, что вместо слов хорошо бы привести код, но без форматирования будет полный отстой.)
> Вот только во фразе lair был пункт «гарантировать… что отданы команды», который вы решили не тестировать.

Ну мы вроде это уже обсудили… Идея не в том, что я по своему призволу решаю, что тестировать, а что нет. А в том, чтобы используя ФП сделать отправку команд тривиальной (гарантированной) и не требующей тестирования. Чтобы достаточно было протестировать только генерацию, которая не требует искусственных интерфейсов и моков.
> Зачем же вы тогда тестируете текст сообщения, там что, есть циклы и условные переходы?

Вспомним, что все началось с фразы lair «гарантировать, что при таком, таком и таком состоянии заказа отданы команды на отправку такого, такого и такого письма. И это реально самый простой, самый быстрый и самый надеждый способ это протестировать». Т.е. нетривиальная логика генерации предполагалась по условию. Я показал более простой, быстрый и надежный способ ее протестировать.
> видно, что вы используете функциональную композицию

Совершенно верно. Я же из упомянутой автором «монструозной» Scala сюда свалился.

> вы используете функциональную композицию, а не объектную

Тут подразумевается некое противопоставление. На деле их можно разумно сочитать.
> В моей личной системе ценностей любое бизнес-требование, реализованное на уровне доменного уровня, должно быть протестировано.

Ничего не имею против (пока вы не в одном проекте со мной). И, обратите внимание, не делаю глубокомысленных заключений о том, понимаете ли вы что-нибудь в чем-нибудь.

> Но как вы гарантируете вызов этого метода в заданных условиях?

Не совсем уверен, что понял вопрос. Но на всякий случай: GeneratePaymentReceivedMessage — «чистая» функция, зависит только от своих неизменяемых аргументов, никаких побочных эффектов внутри.
> Вы действительно не понимаете смысла юнит-тестов

Да я и не претендую даже. Но если поделитесь авторитетными ссылками по поводу проведения черты между очевидными с одной стороны и требующими тестирования с другой вещами — буду благодарен.

> Просто представьте, что код внутри PaymentReceived выглядит вот так

В моем примере код, который отвечает за генерацию сообщений был бы помещен в GeneratePaymentReceivedMessage(). Возвращаемый тип изменился бы на Option[Message]. Тестировался бы по-прежнему GeneratePaymentReceivedMessage(), без IMailer, без моков, без фреймворка для них.
> это происходит в момент получения платежа. Ваш тест этого не проверяет

В моей версии отправка происходит после сохранения платежа. Это очевидно из кода и не требует тестирования.
> кто-нибудь сможет изменить код так, что письмо отправится несколько раз, а тесты все равно пройдут.

> можно изменить код так, что письмо не отправится вообще, а тесты все равно пройдут.

Думаю, я смогу изменить код так, чтобы он создавал background thread, который будет потихоньку удалять папки с диска, забивать память мусором и ддосить сайт виндовс10. И, о ужас, все тесты пройдут.

Повторю другими словами. Тест пытается сломать существующий код, а не гипотетическое его изменение в будущем. Ответственность за тестирование изменения ложится на автора этого изменения.
Да уж куда мне понять смысл юнит-тестирования… Я-то всегда думал, что нет смысла тестировать очевидную последовательность действий. Вот если она станет менее очевидной, например, кто-то добавит циклы или условные переходы — это будет его ответственность исправить тесты соответствующим образом.

Буду благодарен, если опровергните мое скромное и несовершенное понимание ссылкой на какого-нибудь признанного авторитета вроде Бека или Фаулера.
Итак, в представленом юнит-тесте, насколько я понял, проверяется 2 вещи:
1. Метод Send() был вызван 1 раз
2. Сообщение было сгененировано в соответствии с ордером.

Теперь берем изначальный код автора (слегка модифицированный по моему вкусу):

// заполняем объект заказа, cкидки, акции и т.д.
var order = GenerateOrder(....);
SaveOrder(order);
var orderNotification = GeneratePaymentReceivedMessage(order);
SendEmail(orderNotification);

Нужно ли проверять, что SendEmail() вызывается ровно один раз (если ранее не было исключения)? Нет, это очевидно.

Как проверить корректность сообщения:

var order = _CreateValidOrder();
var expectedNotification = _Msg(order.NotificationEmail, «Payment..» + order.Number, «Your order #» + order.Number + "....")
Assert.AreEqual(expectedNotification, GeneratePaymentReceivedMessage(validOrder));

Да, в этом варианте появляется новая сущность — сообщение, но не факт, что это минус.

При этом в этом коде нет IMailer, нет необходимости его мокать, и не используется фреймворк для мокинга.

PS. Прошу прощение за отсутствие возможности отформатировать код.
PPS. Не судите строго, если накосячил с синтаксисом. Последний раз писал на C# в 2008 году.
Стараешься писать с учетом будущей поддержки, накручиваешь все паттерны которые только знаешь для решения существующих и потенциальных проблем… А потом бац, требования поменялись, и пилить-то совсем другое надо, как оказалось. И код отправляется в помойку, независимо от всех стараний.

Я не утверждаю, что так бывает всегда, на всех проектах. Но бывают проекты, где решить текущую задачу быстро и просто важнее, чем думать о поддержке, которой может никогда и не случиться, если нет новых фич, нет привлеченных ими пользователей, и фирма обанкротилась.
Тогда давайте начнем с начала — покажите код юнит-теста. Как и что именно он будет проверять? Если я правильно понял изначально процитированные слова о том, что нужно проверить — я покажу как его упростить.
В самую точку! Конечно я ненавижу чужой код да и свой собственный за компанию. И если нахожу даже единственную несчастную опечатку, то разумеется удаляю сразу все, вместе с репозиторием.

Теперь серьезно. Если я вижу подсистему, которая в силу проблем архитектуры/дизайна не может справиться с возложенными задачами, бывает, что проще переписать с нуля, чем расшибаться в лепешку и лепить костыли вокруг очевидных косяков. Считаете, это всегда будет дороже? Почему?
> Куда выдает?

Той самой void-функции с побочным эффектом, который будет заключаться в том, чтобы сунуть эти команды в Send() мейлеру. Заодно еще будет выдавать юнит-тесту.
Иметь метод, который выдает команды в зависимости от состоянии заказа. Интерфейс не нужен, мок мейлера не нужен. Взяли коллекцию команд и проверили.
> гарантировать, что при таком, таком и таком состоянии заказа отданы команды на отправку такого, такого и такого письма. И это реально самый простой, самый быстрый и самый надеждый способ это протестировать.

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

Ну, в моей практике случалось, что благодаря высокой скорости разработки было проще и дешевле выкинуть и переписать с нуля, чем поддерживать. Кстати, особенно тяжело как выкидывать, так и поддерживать код с избыточным и неадекватным применением паттернов. Но это так, музыка навеяла.

Информация

В рейтинге
Не участвует
Откуда
Россия
Дата рождения
Зарегистрирован
Активность