Информация об изменениях

Сообщение Re[46]: Что такое Dependency Rejection от 19.01.2024 17:59

Изменено 19.01.2024 18:34 ·

Re[46]: Что такое Dependency Rejection
Здравствуйте, Pauel, Вы писали:

P>>>Это я имею ввиду начинку — то, что вы заложите в мок. С параметрами у нас ровно самый минимум — только таблица истинности, и сам код. У вас к этому добавляются еще и моки.

P>·>И пусть добавляются. Это просто способ передачи значений.
P>Это дополнительные издержки по сравнению с простыми функциями. Вы это сами показали на тестах календаря.
Ты просто пытаешься ввести в заблуждение. Да, у меня в тестах добавляется одна строчка кода и собственно всё. Зато в твоём решении добавляется N-строчек в _прод_ коде и требуется больше тестов. Иными словами, ты "убираешь" код, но стесняешься говорить _куда_ этот код убирается.

P>>>То-то и оно. Вот вы снова сами себе и ответили

P>·>Причём тут тогда "такую функцию"? Интеграция это не "такая функция".
P>Ощущение что для вас функциональные требования и функция в джаве это одно и то же. Как вы смоделируете, например, функцию удаления пользователя в системе — дело десятое. Это всего одна строчка в функциональных требованиях. А в вашей архитектуре это может быть процесс растянутый на месяц — пишете в очередь, читаете из нее, удаляет порцию, пишете в очередь... и тд и тд.
P>И у вас вполне вероятно не будет ни одной функции в джаве "удалить пользователя". А будет например "дропнуть док по хешкоду", "дропнуть записи по номеру телефона", "дропнуть сообщения по емейлу", "отозвать сертификат", "пометить спейс юзера к удалению через месяц", "подготовить выгрузку всех данных юзера", "закинуть зип на aws s3 c ttl 1 месяц"
Ну будет же где-то какой-то код, который отвечает за бизнес-действие "удалить пользователя", который в итоге сделает всё вот это перечисленное. Почему этот код нельзя собрать в одну языковую сущность и назвать "удалятором пользователей", вызать оттуда эти 7 шагов в нужном порядке с нужными параметрами, покрыть тестами — я не понимаю. В твоём мире ты, наверно, в чатике клиентам пошлёшь инструкцию из этих 7 шагов, да? Поэтому такого кода у тебя не будет, да? Или как?

P>·>Вас может прибивают, нас — не прибивают.

P>Вас тоже прибивают, только вы в этом видите пользу какую то.
Нет, не прибивают. Мы используем моки не так, как ты фантазируешь.

P>>>Этот пример демонстрирует особенность — даже ИДЕ перестает помогать.

P>·>Помогать в чём? Ещё раз — инлайнинг и т.п. — это средство, а не цель. С какой целью что инлайнить-то?
P>Рефакторинг, очевидно. Его не раз в год нужно делать, а непрерывно — требования меняются постоянно.
Опять путаешь цель и средство. Рефакторинг — это средство.

P>>>А вот если вы отказываетесь от моков, соотвественно, ИДЕ вам в помощник — вдруг рефакторинг inline начинает работать сам собой

P>·>Ок, расскажи от каких моков надо избавиться, чтобы рефакторинг inline для str.length() заработал?
P>Элементарно — замена вызова на константу. Это работает, т.к. str у вас иммутабельная. А вот текущее время такой особенностью не обладает.
На какую константу? Значение length() зависит от того, что лежит в данном конкретном экземляре str:
Result validateUsername(String username)
{
   if(username.length() > 100) return Error("Too long!");
   else return Ok();
}

Давай, покажи свой супер-полезный инлайн рефакоринг!

P>Другая проблема — ваша реализация с зависимостью внутри кешированию не поддаётся.

Кешированию чего? Куда прикручивается кеш — надо смотреть на полное решение, а не на данную конкретнную строчку кода. В твоём решении проблема с кешем будет в другом месте, и её решать будет гораздо сложнее. Точнее ты вообще не сможешь кеш прикрутить, т.к. аргумент now — это текущее время, ВНЕЗАПНО. Ты ключoм в кеше будешь делать текущее время??
А у меня всё круто, т.к. реализация nextFriday() знает, что это отностися к следующей пятнице, то может кешировать одно и то же значение в течение недели и обновлять его по таймеру, например.

P>То есть, проблемы с вашей реализацией мы видим, а вот бенефиты какие то мифические, и вы никак не можете их описать.

Ага-ага. Бревно в глазу не замечаешь.

P>>>Дизайн вообще должен меняться постоянно, непрерывно. А вы выбираете такой подход, что изменения раз в год-два. О том я и говорю — вы пилите монолит.

P>·>Зачем?! Это ты из мира js говоришь, где каждую неделю новый фреймворк выходит и надо всё переписать? А каждые пол года очередной самый правильный дизайн появляется от фаулеров и надо срочно всю архитектуру перекраивать?!
P>Из вас технологический расизм прёт непрерывно. Извините, но на мой взгляд вместе с телепатией это признак неполной профессиональной компетенции.
P>Дизайн меняется по простой причине — требования меняются непрерывно. Вот появилось нефункциональное требование "перформанс" и мы видим, что CalendarLogic сидит в hotpath и вызывается сто раз с одним и теми же значениями. Опаньки — меняем дизайн.
Не меняем дизайн, а оптимизируем внутреннюю реализацию одного метода, ну того конкретного nextFriday(). Менять дизайн на каждый чих — признак полной профессиональной некомпетенции дизайнера решения. Разуй глаза — в сигнатуре "LocalDate nextFriday()" — совершенно не говорится что откуда должно браться — считаться на лету, из кеша, запрашиваться у пользователя или рандомом генериться от погоды на марсе.

P>Как это сделать в вашем случае — не ясно. Разве что прокинуть к вашему CalendarLogic еще и MRU, LRU кеш с конфигом "вот такие значения хранить пять минут". Вот уж простор для моков — теперь можно мокать не одно время, а целых три, а можно MRU, LRU обмокать.

Толи ты вообще в дизайн не умеешь, толи нарочно какую-то дичь сочиняешь, чтобы мне приписать для strawman fallacy.

P>>>Соответсвенно нужно выбирать такое решение, которое проще мейнтейнить, с учетом тестов.

P>·>Угу. И моки тут помогают.
P>Что бы просто так, без контекста — это слишком сильное утверждение. Но в целом оно отражает ваш подход к тестированию
Моки — это ещё один дополнительный инструмент в копилку. Больше инструментов — больще возможностей решить задачу более эффективно.

P>>>Вот код теста — как только вы его написали, решение о том, как будут протягиваться зависимости, уже принято

P>·>ТДД диктует необходимость того, чтобы код был тестируемый, а не про то куда как тянуть зависимости. Единственно что он может диктовать, что зависимостями надо управлять явно, чтобы можно было прокидывать тестовые.
P>Как только вы написали тест, то из него однозначно следует, какой будет АПИ и как будут прокидываться зависимости. Вы поймите — код теста это выражение наших намерений
P>1 апи
P>2 зависимости
P>3 поведение
P>4 связывание
P>5 входы-выходы.
P>По большому счету, идеально свести тест к п5. А вы сюда втаскиваете лошадь размером со слона.
апи диктуется требованиями клиентского кода, а не тестами. А зависимости — это уже как мы обеспечиваем работу этого апи.

P>>> 1,2,3,4,5,6 // и так на каждую строчку в табличке

P>·>Не понимаю. Ты, похоже, стиль кода путаешь с дизайном. А дизайн с управлением зависимостями. Ну сделай себе таблицу истинности и с моками, и с параметрами:
P>·>
P>·>   when(dep.getX()).thenReturn(x);
P>·>

P>Лишняя строчка
Прям ужас-ужас. Но это не главное, главное ты наконец-то таки согласился, что твои "таблицы истинности", не пострадали от моков.

P>>> Только еще вводите понятие wiring code.

P>·>Ок. Возможно я использовал неизвестную тебе терминологию. Это из мира управления зависимостей. "wiring up dependencies", "autowiring", под я под wiring code здесь имел в виду "composition root".
P>Вот-вот. Статься от 2011го, ссылается на книгу, которую начали писать в 00х на основе опыта накопленного тогда же.
И? Поэтому я и не понимаю как эта терминология мимо тебя прошла.

P>Мы только что выяснили, кто же из нас двоих вещает из нулевых

Вот от твоего любимого фаулера, свежачок: https://martinfowler.com/articles/dependency-composition.html

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

P>·>Да похрен на дизайн. Это лишь меняет место где "волшебство" происходить будет.
P>Вам похрен — это и ежу понятно. Отличия в дизайне позволяют применить более эффективные способы, например, тестирования.
Да применяй, я ж не запрещаю. Я ж говорю — похрен какой дизайн.

P>>>Прокидываете now в контролере — эту часть логично исключить из dependency injection.

P>·>В контроллере now откуда возьмётся?
P>Вы уже много раз задавали этот вопрос, и я вам много раз на него отвечал. Отмотайте да посмотрите.
Я код прошу показать, но ты упорно скрываешь.

P>>>Смысл помещения кода связывания в контроллер в том, что бы легче было интеграцию тестировать, делая самое важно явным.

P>·>Не понял, чем легче?
P>Самое важное сразу в коде метода, а не раскидано абы где по всеей иерархии dependency injection.
Субъективщина какая-то.

P>>>У вас CalendarLogic принимает зависимость к конструкторе. Технически, эта зависимость может быть и null, и предзаписаной, и привязаной к контексту — не принципиально.

P>·>Это тут причём? Технически передавая через параметр твой now — тоже может быть null и т.п. Всё то же самое.
P>Нет, не может быть. Это вам нужно делать явно в самом контроллере, который зафейлится сразу же, на первом тесте который его вызовет.
Дык я об этом и талдычу, притом не в абы каком тесте, а конкретно в тесте который тестирует этот конкретную функцию этого конкретного контроллера. А вот в моём случае это придётся делать тоже явно в коде связывания зависимостей и зафейлится если не компилятором, то во время старта, ещё до того как дело дойдёт до запуска каких-либо тестов.

P>·>В твоём же случае впихивание null будет происходить только при вызове функции приложения. Поэтому вам и требуются тесты всего уже после "успешгого" деплоя в проде.

P>Я ж вам объяснил — тесты прода нужны для самых разных кейсов.
Они не "нужны". Есть другие подходы, которые покрывают эти же кейсы (по крайней мере, подавляющее большинство), но не требуют прогона тестов.

P>·>А как ты будешь такой и-тест писать, если у тебя будет Time.Now? Что ассертить-то будешь? Как отличать два разных вызова Time.Now — клиентский от серверного? Впрочем, ты уже ответил "никак". Или я что-то пропустил?

P>Согласно бл. А вы как собираетесь отличать два разных вызова, клиентский от серверного? Вам для этого придется намастырить кучу кода с инжекцией в конструктор, только всё важное будет "где то там"
Я уже раза три рассказал. Клиентский и серверный будут иметь разный ожидаемый результат.

P>>>Вы похоже сами свой код не читали, т.к. у вас недоумение, откуда может быть null. И ежу понятно — будет передан в конструктор или явно, или неявно. Правильно это или нет — скажут тесты.

P>·>Так же можно явно или неявно передать now=null в твой nextFriday(now).
P>Теоретически — можно. Практически — первый интеграционный тест сваливается, когда вызывает этот контроллер.
Если этот тест не забудут написать, конечно. Покрытие-то ты анализировать отказываешься...

P>>>А щас вы расказываете басню, что компилер как то отличит один провайдер времени от другого времени с таким же интерфейсом

P>·>Компилятор может отличать имена символов. Это же в коде видно и на ревью всё очень очевидно — где какое поле куда передаётся.
P>Вот вы сами себе и ответили. Ну или объясните, почему код ревью перестанет работать с параметром в контроллере.
По ревью контроллера не видно какой параметр должен туда передаваться и можно ли или нельзя ли туда загонять null. Вот тут:
load: () => repo1,
load2: () => svc,

Почему один load должен быть такой, а другой сякой? А null туда можно или нет? И т.п. И это всё технические детали внутренней реализации, которые к бизнес-требованиям отношения не имеет. И это всё прод-код, покрытый какими-то тестами, которые неясно как относятся к этому коду и выполняют ли его вообще.
В моём случае:
when(timeProviders.systemClock()).thenReturn(TestData.SystemClock);

я тут явно вижу, что системные часы возвращают именно тестовое системное время.
assert someResult1.dateAAA == TestData.SystemClock.now();

Тут я уже как ревьювер могу понимать требования бизнеса могу понять, или заглянуть в джиру, чтобы проверить, что dateAAA должно соответствовать системному времени, а не времени события. И это код тестов, который описывает ожидания.

P>>>Прямой вызов — ваша функция вызовется прямо сейчас. Отложеный — потом. Например, вы сначала делаете запись, что за функция вызовется, с какими параметрами и тд, а в нужный момент сервис делает собственно сам вызов.

P>·>Какое это имеет отношение к разговору?
P>Вы же спрашиваи, что такое отложенный вызов. Если у нас нет зависимости внутри, то отложеный вызов делается как два пальца об асфальт. Это снова про гибкость, которой у вас почему то нет.
Если ты не понимаешь как это делать так спрашивай, а не фантазируй отложенно.

P>>>Просто разные аргументы.

P>·>Аргументы чего? Откуда ты знаешь, что значения этих аргументов именно Time.Now и как это проверишь в тесте?
P>С вашего аккаунта похоже разные люди пишут, да еще и меняются во время написания одного и того же сообщения. Вот некто с вашего аккаунта чуть выше ответил на ваш вопрос:
P>

P>Компилятор может отличать имена символов. Это же в коде видно и на ревью всё очень очевидно — где какое поле куда передаётся.

И как это проверишь в тесте?
И я написал почему в твоём коде это неочевидно на ревью.

P>>>·>Что синглтоны — плохо учат ещё в детском садике, ясельной группе.

P>>>Вы наверное не поняли — никаких синглтонов, только параметры.
P>·>Откуда возьмутся значения этих параметров во всех местах вызова?
P>Через параметры функции. Дизайн то плоский, а не глубокий, как у вас.
Через параметры значения передаются. Откуда они возьмутся-то?

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

P>·>Надо где-то тестировать, что ты протаскиваешь именно Time.Now а не что-то другое.
P>Вам тоже надо тестировать что вы привязали в проде пров клиенского времени, а не пров серверного или предзаписаного. Похоже, у вас там снова смена автора на вашем аккаунте.
Точка привязки будет ровно одна. К символьному имени systemClock будет привязка физического серверного источника времени. А все остальные привязки в бизнес-логике будут покрыты тестами: что ожидаемые значения в результатах связаны с тем же именем assert someResult1.dateAAA == TestData.SystemClock.now(). Если очень хочется, то и привязку физического источника времени можно будет проверить одним _системным_ тестом на всю систему, а не на каждый метод контроллера.
У тебя точек привязки — в каждом месте использования nextFriday и никак не тестируется вообще.

P>>>Интеграционным, которых ровно столько же, сколько и у вас.

P>·>Нет. У меня пирамида тестов. А у тебя — либо цилиндр, либо дырявое решето.
P>Для пирамиды нужно большое количество дешовых юнит тестов. У вас много полу-интеграционных, на моках, и мало собственно чистых юнитов. У меня — наоброт. Потому именно ваша пирамида видится мне ромбиком.
У меня "дешевые" в том смысле, что они выполняются очень быстро. Так вот даже "полу-интеграционные на моках" — выполняются очень быстро, за минуты всё. А у тебя "дешевые" — выполняются полтора часа, но называешь их "дешевыми" так просто, потому что тебе так их нравится называть, без объективных на то причин.

P>>>·>Если при этом нужно плодить копипасту, то это явно что-то не то. И неясно причём тут инлайн и гибкость кода. str.length() — это негибкий код?!

P>>>Вы сейчас перевираете.
P>·>Неперивираю, ты просто свои слова забыл. Напоминаю: " гибкость, которая проявляется в т.ч. в том, что код можно трансформировать, инлайнить". А дальше чистая логика:
P>·>"гибкий код → можно инлайнить" => "инлайнить нельзя → не гибкий код" см. https://en.wikipedia.org/wiki/Contraposition
P>С str.length всё в порядке — инлайнится с переводом в константу. У вас что то еще?
Ну зайнлайнь. Пример кода я тебе выше привёл.

P>>>Речь про инлайн вашего nextFriday(). Об ваш супер-крутой подход почему то ИДЕ спотыкается и вы проблемы в этом не видите.

P>·>Я спрашиваю "зачем инлайн"? Какую задачу-то решаем этим инлайном?!
P>Рефакторинг, оптимизации.
Рефакторинг это тоже не задача. А инлайнить для оптимизаций только самые ленивые компиляторы в режиме дебага не умеют.

P>>>Ваши тесты проверяют только тривиальные кейсы, на которых все зелено. В проде как правило кучка всего подряд.

P>·>Ну добавь эту твою кучку в тесты. Вопрос-то в чём?
P>Эта кучка называется data complexity. Я, например, билдером могу гарантировать, что данные будут запрашиваться постранично. Или что всегда будет фильтр по времени, где период не более трех месяцев.
Не очень понял, как гарантировать и что именно? И почему то же самое нельзя гарантировать другими способами? И вообще это всё больше похоже на бизнес-требования, чем на проверки каких-то кейсов в тестах.

P>Соответсвено тестами можно проверить что структура запроса та, что нам надо

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

P>>>О том, что фильтр может вернуть много записей, вам может сказать или сам запрос, или его результат.

P>·>Как запрос может что-то сказать?
P>Элементарно — всегда есть шанс, что ваш запрос вернет чего нибудь лишнее. Тестами эта задача не решается, принципиально.
P>Что бы написать корректный запрос, вам надо
P>1. гарантировать, что бы на "тех" данных вернется что надо. Это можно решить тестами — накидать данных, обработать, сравнить результат
P>2. гарантировать, что на других данных, которых у вас нет, тоже вернется ровно то, что надо. И тестами на основе данных это не решается — т.к. данных нет.
P>Итого — ваши тесты данных не являются решением, т.к. п 2 у нас обязательный. А вот дополнительные методы, например, инварианты, пред, пост условия, контроль построения запроса, код ревью основанное на понимании работы бд — это дает более-менее осязаемые данные.
Я не знаю на какой вопрос ты ответил, но я напомню что я спрашивал: "Как запрос может что-то сказать?". Контекст выделил жирным выше.

P>>>Второй вариант у вас отпадает, т.к. в тестах у вас не будет хрензнаетсколькилион записей в тестах

P>·>Зачем мне столько записей? В простом случае понадобится две записи — та которая я ожидаю, что она должна вернуться и та которая я ожидаю, что не должна вернуться.
P>Я вам выше рассказал. Нужна гарантия, что не вернется вдруг чего нибудь не то. Вариантов "не того" у вас по скромной оценке близко к бесконечности.
Как ты это предлагаешь _гарантировать_? И причём тут тесты?

P>>>Это вы собираетесь базу тестировать

P>·>Опять врёшь. И цитаты, ясен пень, не будет.
P>Вы топите за косвенные проверки запросов, тестируя связку репозиторий+бд. Вот этого оно и есть.
Я топлю за то как надо писать тесты, чтобы от них была хоть какая-то польза и поменьше вреда. А не абстрактные "проверки" которые что-то должны "гарантировать" посредством говорящей рыбы запроса.

P>>> и топите за поведение. А мне надо всего лишь построение запроса к орм проверить. Если орм не позволяет такого — надо смотреть его sql выхлоп, если и такого нету, значит придется делать так, как вы предлагаете.

P>·>"Надо" кому? Мы вроде говорим о тестировании ожиданий бизнеса. Ты правда считаешь, что пользователи ожидают определённый выхлоп sql?!
P>Пользователи ожидают, что на их данных, которых в ваших тестах нет, будет нужный результат.
Если ожидания есть, то какая проблема их добавить в тесты?

P>Вашими тестами эта задача не решается

Я показал как решение этой задачи можно тестировать моими тестами. Ты так и не рассказал как она решается вашими тестами.

P>>>И всё это нужно покрывать тестами, иначе любой дядел поломает цивилизацию

P>·>Ещё раз. У тебя в коде теста, в ожиданиях находится "in: [fn1]" и т.п. Как это соответствует "возвращать именно те данные, что прописаны в бл"? Где в бл вообще будет фигурировать in, fn1 и прочий select?
P>Не нужно сводить всё к бл напряму. У вас технологического кода в приложении примерно 99%. Он к бл относится крайне опосредовано — через связывание. А тестировать эти 99% тоже нужно.
Именно. Ты меня обвинил в том, что у меня тесты тестируют детали реализации, а на самом деле ты просто с больной головы на здоровую. Но зато у тебя дизайн самый модный!

P>>>Смотрите выше — вы фильтры предлагаете тестировать однострочными вырожденными кейсами. Как вы будете ловить те самые комбинации, не ясно.

P>·>Ну я не знаю твой код. Я разрешаю тебе писать кейсы двухстрочные и даже трёхстрочные!
P>Вы что, недогоняете, что нет возможности покрыть неизвестные данные и комбинации какими либо тестами?
Зато есть возможность покрыть известные данные и комбинации тестами. Вы и этого не делаете, а просто имена приватных функций ассертите и буковки сравниваете (тоже, кстати известные). Вот и приходится часами билдить и в проде тесты гонять чтобы хоть что-то как-то обнаружилось.

P>>>Ни то, ни другое проблему с фильтрами не найдут.

P>·>Почему?
P>Потому, что у вас нет всего множества данных, которые могут приходить от юзеров.
P>Более того — количество данных дает вам такую особенность, как слишком долгое время выполнения запроса.
P>Задача с фильтрами у меня выявила такую вещь — если данных слишком много, то запрос с секунд резко уезжает в минуты.
P>Вот оптимизацию такой вещи закрыть тестами, что бы первый же фикс не сломал всю цивилизацию
Это всё круто. Каким образом твоё "in: [fn1]" обеспечивает тебя всем множеством данных которое может приходить от юзеров?

P>>>т.е. тесты контролера зависят от мока

P>·>Т.е. в тестах контроллера мы прописали ожидание, что время используется серверное, в соответствии с бизнес-требованиям.
P>В интеграционном тесте можно сделать то же самое.
Как? Код в студию. Вот в куче кода кучи контроллеров где-то у тебя написано Time.Now. Как ты это проверишь в интеграционном тесте что некий результат содержит именно что-то вычисленное от Now, а не что-то другое?

P>>>Вторая — вызываем бл при чтении из очереди, при этом время должно быть клиенское.

P>·>Т.е. это значит, что _изменились_ бизнес-требования и теперь мы должны брать не серверное время, а клиентское. Ясен пень, это фукнциональное изменение и соответствующие тесты надо менять. Если у тебя ни один тест не упадёт, у меня для тебя плохие новости.
P>Это как раз нефункциональное требование. Например, у нас запрос выполнялся слишком долго, и мы процессинг вытолкнули в очередь с отложеным процессингом.
Не это, а то что вначале мы использовали серверное время, а потом вдруг стали использовать клиентское — это изменение функциональное, т.к. источники времени — принципиально разные и влияют на наблюдаемый результат. Если бы было время клиентское изначально, то никаке функциональные тесты не пострадают.

P>>>А это значит, что ваши тесты контролера идут в топку, как и моки которые вы под это дело прикрутили.

P>·>В контроллере надо будет заменить использование systemClock() на eventTimestampClock() и поправить тесты, чтобы зафиксировать функциональное изменение.
P>Функционых изменений здесь ровно 0. С т.з. пользователя никаких функций не добавляется.
Меняется наблюдаемое поведение и вообще может быть дырой в безопасности. Клиентское время, скажем, можно захакать.
А если в твоём случае systemClock() и eventTimestampClock() — вещи функционально неразличимые, то вообще неясно зачем ты тут с меня трясёшь как я их буду различать.

P>>>зачем?

P>·>Чтобы понять какое отношение имеет метапрограммирование к тестированию ожиданий бизнес-требований.
P>Метапрограммирование дает возможность зафиксировать свойства запроса, которые вы с вашими тестами сделать не сможете, никоим образом.
А ещё я кофе моими тестами сварить не смогу. Что сказать-то хотел?
Напомню, мы сравниваем тестирование ожиданий бизнес-требований твоими тестами с "in: [fn1]" vs моими тестами c "save/find". Так причём тут метапрограммирование?

P>>>Очевидно, посыплются не все. Ну нет такой ситуации что бы билдер на каждый запрос лепил UNION. Отвалится скорее всего несколько кейсов, ради которых это все и затевается.

P>·>Ну этот кусочек может быть в большой группе запросов, выбирающих разные данные по-разному, но часть логики — общая.
P>Может. Это дополнительные гарантии, сверх тех, что вы можете получить прогоном на тестовых данных. Потому издержки обоснованы.
Гы. Опять у тебя тесты гарантии дают.

P>>>Кто даст гарантию, что в проде вы правильно прописали зависимости для CalendarLogic? Интерфейс то один и то же

P>·>Гарантию, конечно, никто не даст, но покрыть тестами с моками тоже можно. Для этого моки и придумали.
P>Вот вот — прибиваете контроллер к тестам на моках.
Ты так говоришь, как будто это что-то плохое.

P>>>Буквально curl тест все что может, это сравнить выхлоп, и всё. Если там время не фигурирует, то никак.

P>·>Ну, допустим, фигурирует. И чем это тебе поможет?
P>Тогда можно сравнить выхлоп, тот или не тот.
Так я тебя и спрашиваю как ты будешь сравнивать выхлоп зависящий от Time.Now — тот он или не тот?

P>>>Если же вы имеете ввиду интеграционные, то будет все ровно как и у вас.

P>·>С моками? Ну дык об чём спор-то тогда? Ведь именно моком я могу протащить тестовые значения now в нужные места. Там, где ты предлагаешь Time.Now синглтон, в который ты ничего протащить не сможешь.
P>Этим протаскиванием вы бетонируете весь дизайн. Смена дизайна = переписывание тестов.
Нет.

P>>>Бинго! Оптимизация, нефункциональные требования, технические особенности — все это имеет чудовищное влияние, намного больше, чем сама бизнес-логика.

P>>>Если вы потеряете оптимизацию — приплызд. и тест нужен зафиксировать эту особенность что бы сохранить при изменениях.
P>·>Это уже будет перф-тест и к обсуждаемой теме не относится.
P>Покрыть перформанс тестами все вырожденные кейсы вам время жизни вселенной не позволит. Некоторые детали реализации нужно фиксировать обычыными тестами.
Обычные тесты не могут тестировать перф. А детали реализации лучше не тестировать.

P>>>См выше — все нефунциональные вещи нужно проверять и фиксировать тестами.

P>·>Это не оправдывает твой подход, что функциональные тесты должны тестировать метапрограммирование.
P>Метапрограммирование тестируется там, где оно применяется в силу естественных причин. Если у вас запросы тривиальные, вам такое не нужно.
Ты не понял. Надо тестировать не метапрограммирование, а ожидания.

P>>>Покажите, как вы все эти кейсы обнаруживать будете.

P>·>Возвращаемся к старому спору. Я уже отвечал на это неоднократно: системы мониторинга, health probes и прочие операционные инструменты. Это не тестирование.
P>Неэффективно. Для многих приложений час невалидной работы может стоит любых денег. Дешевле запустить тесты на проде — час-два и у вас информация по всем функциям.
Ну никто нам не позволит делать "тестовые" сделки на прод системах. Каждая сделка — это реальные деньги и реальная отчётность перед регуляторами. Так что "информацию по всем функциям" нам приходится выдавать ещё до аппрува релиза.

P>>>Полагаю ответ будет "у нас багов на проде нету" ?

P>·>Вот таких багов "протух-обновился сертификат" — и не должно быть. Такие вещи именно мониторингом отслеживаются заранее, чтобы пофиксить можно было до возникновения этой ситуации. А у вас серты протухают ВНЕЗАПНО, и обнаруживаются только тестами постфактум, что у вас в проде что-то где-то отвалилось... ну могу только пособолезновать вашим клиентам.
P>Вы снова включили телепатию. Сертификат может обновиться не у вашего приложения, а у какого нибудь из внешних сервисов. Мониторинг вам скажет, когда N юзеров напорются на это и у вас начнет расти соответсвующая метрика.
Такое — major incident и соответствующий разбор полётов с пересмотром всех процессов — как такое допустили и определение действий, чтобы такое больше не произошло.

P>>>насколько я понимаю, это я все еще жду вашего примера про фильтры, который решает ту самую data complexity

P>·>Какую ту самую? Я никакой "data complexity" не видел и телепатией не обладаю, чтобы выдать какое-то решение для задачи о которой я не слышал.
P>У вас есть иллюзия, что ваши тесты гарантируют отсутствие лишнего выхлопа на данных, которые у вас отсутсвуют.
У меня такой иллюзии нет, ты опять насочинял, я такое нигде не писал. О гарантиях в тестах фантазировать любишь только ты.

P>>> Вход на фильтры полным перебором подавать или магией воздействовать?

P>·>Да так же, как у тебя. Только ассертить не буквальный текст запроса, а результат его выполнения
P>
Угу, вот так вот просто.

P>>>Вы когда нибудь видели дашборды где фильтры можно выбирать хрензнаетсколькилионом кнопочек и полей ввода?

P>·>Насколько мне доводилось видеть, они обычно через & соединяются. Т.е. список независимых фильтров. Сложность возникает когда фильтры между собой взаимосвязаны, но обычно такое избегают, т.к. юзерам такая compexity тоже не под силу, и использовать нормально всё равно не получается.
P>Ну да, вы сами чего то додумали, и воюеете за это
Потому что ты никогда задачу не обозначил. Задаёшь какие-то наводящие вопросы.

P>>>Не просто манифест — а любое описание, абы ваши пиры могли его понять и цикл разработки был быстрым.

P>·>Что значит за любое описание? Манифест это и есть описание api.
P>Любой формат, любое расширение файла У ваших пиров должен быть инструмент для использования такого описания.
P>Например, вы решили выдать swagger, но описали в ём такое апи, которое ни один генератор кода не понимает. Это значит, что вы АПИ вообще не предоставили — мало кто в своём уме будет вручную имплементать нюансы которые к релизу поменяются 100500 раз
Ну я хотя бы могу начать допиливать этот самый swagger, чтобы генератор таки стал понимать.
А у тебя всё гораздо хуже. Ты пишешь аннотации, конфиги, тенантов, реализацию, веб-сервер, потом у тебя наконец-то http-запрос выдаётся этот же самый swagger по этим аннотациям, который ВНЕЗАПНО ни один генератор кода не понимает. И это реальная ситуация, которую помнится уже обсуждали пару лет назад и твоим решением было "ну пусть клиенты пишут всё ручками", т.к. перепилить свои аннотации и http-запрос ты уже не сможешь, ибо проще пристрелить.

P>>>Появятся после того, как вы заделивирите АПИ, например спустя год, когда у вас уже версия 1.x. И вот здесь вы решите перейти на rust, то в этот момент крайне странно топить за design-first — у нас уже готовое приложение версии 1.x, и нужно сделать так, что бы манифест отрендерился — с учетом тенантов и прочих вещей, включая not implemented. Или вы собираетесь бегать по отделам и рассказывать "а чо вы это делаете — мы же на джаве год назад это решили не делать" ?

P>·>У вас уже есть имплементация и мы вот наконец спустя год выкатываем манифест api, но ты всё ещё это называешь design-first?! Нет, это типичный code-first — когда прыгать надо, думать некогда.
P>Вы похоже не читаете. Какой смысл вам что либо рассказывать?
P>1. когда стартуем проект, где центральная часть это АПИ. Идем через design-first, формат файла и расширение — любое, см. выше
P>2. готовый проект — уже поздно топить за design-first, просто генерим артефакты
P>3. переписываем проект на rust — снова, нам design-first не нужен, это вариация предыдущего кейса
Не понял, как из 1 получилось 2?

P>Вот если у вас переписывание случается каждый день, или даже чаще, да на новый язык, то АПИ нужно держать в каком то универсальном формате.

P>Правда, в этом случае я более чем уверен, вы дольше всего будете подбирать генераторы кода под это АПИ, или выписывать все загогулины руками с воплями "я уже заимплементал херли вы перенесли параметр из квери в хидеры!!!!!!!11111йййкукуку"
Ну я о чём и говорю. У вас "думать некогда, прыгать надо". Пишете аннотации, код, выкатываетет, потом как всегда ВНЕЗАПНО "поздно топить за design-first"

P>>>Я вам говорю откуда возьмется апи в готовом, если вы решите уже готовое переписать на другом языке.

P>·>Причём тут design-first?
P>У вас точно меняется контингент, или в голове, или за компом:
P>

P>Если вы захотите переписать свой сервис на другой ЯП, ну там с js на rust — что вы будете делать со своими аннотациями?

P>Переписывание предполагает, что у нас уже есть и АПИ, и реализация этого АПИ. Отсюда ясно, что проще сгенерировать артефакт, чем изначально держаться за конкретный формат и топить за design-first
Ага, верно, проще прыгать чем думать. Не спорю.
Re[46]: Что такое Dependency Rejection
Здравствуйте, Pauel, Вы писали:

P>>>Это я имею ввиду начинку — то, что вы заложите в мок. С параметрами у нас ровно самый минимум — только таблица истинности, и сам код. У вас к этому добавляются еще и моки.

P>·>И пусть добавляются. Это просто способ передачи значений.
P>Это дополнительные издержки по сравнению с простыми функциями. Вы это сами показали на тестах календаря.
Ты просто пытаешься ввести в заблуждение. Да, у меня в тестах добавляется одна строчка кода и собственно всё. Зато в твоём решении добавляется N-строчек в _прод_ коде и требуется больше тестов. Иными словами, ты "убираешь" код, но стесняешься говорить _куда_ этот код убирается.

P>>>То-то и оно. Вот вы снова сами себе и ответили

P>·>Причём тут тогда "такую функцию"? Интеграция это не "такая функция".
P>Ощущение что для вас функциональные требования и функция в джаве это одно и то же. Как вы смоделируете, например, функцию удаления пользователя в системе — дело десятое. Это всего одна строчка в функциональных требованиях. А в вашей архитектуре это может быть процесс растянутый на месяц — пишете в очередь, читаете из нее, удаляет порцию, пишете в очередь... и тд и тд.
P>И у вас вполне вероятно не будет ни одной функции в джаве "удалить пользователя". А будет например "дропнуть док по хешкоду", "дропнуть записи по номеру телефона", "дропнуть сообщения по емейлу", "отозвать сертификат", "пометить спейс юзера к удалению через месяц", "подготовить выгрузку всех данных юзера", "закинуть зип на aws s3 c ttl 1 месяц"
Ну будет же где-то какой-то код, который отвечает за бизнес-действие "удалить пользователя", который в итоге сделает всё вот это перечисленное. Почему этот код нельзя собрать в одну языковую сущность и назвать "удалятором пользователей", вызать оттуда эти 7 шагов в нужном порядке с нужными параметрами, покрыть тестами — я не понимаю. В твоём мире ты, наверно, в чатике клиентам пошлёшь инструкцию из этих 7 шагов, да? Поэтому такого кода у тебя не будет, да? Или как?

P>·>Вас может прибивают, нас — не прибивают.

P>Вас тоже прибивают, только вы в этом видите пользу какую то.
Нет, не прибивают. Мы используем моки не так, как ты фантазируешь.

P>>>Этот пример демонстрирует особенность — даже ИДЕ перестает помогать.

P>·>Помогать в чём? Ещё раз — инлайнинг и т.п. — это средство, а не цель. С какой целью что инлайнить-то?
P>Рефакторинг, очевидно. Его не раз в год нужно делать, а непрерывно — требования меняются постоянно.
Опять путаешь цель и средство. Рефакторинг — это средство.

P>>>А вот если вы отказываетесь от моков, соотвественно, ИДЕ вам в помощник — вдруг рефакторинг inline начинает работать сам собой

P>·>Ок, расскажи от каких моков надо избавиться, чтобы рефакторинг inline для str.length() заработал?
P>Элементарно — замена вызова на константу. Это работает, т.к. str у вас иммутабельная. А вот текущее время такой особенностью не обладает.
На какую константу? Значение length() зависит от того, что лежит в данном конкретном экземляре str:
Result validateUsername(String username)
{
   if(username.length() > 100) return Error("Too long!");
   else return Ok();
}

Давай, покажи свой супер-полезный инлайн рефакоринг!

P>Другая проблема — ваша реализация с зависимостью внутри кешированию не поддаётся.

Кешированию чего? Куда прикручивается кеш — надо смотреть на полное решение, а не на данную конкретнную строчку кода. В твоём решении проблема с кешем будет в другом месте, и её решать будет гораздо сложнее. Точнее ты вообще не сможешь кеш прикрутить, т.к. аргумент now — это текущее время, ВНЕЗАПНО. Ты ключoм в кеше будешь делать текущее время??
А у меня всё круто, т.к. реализация nextFriday() знает, что это отностися к следующей пятнице, то может кешировать одно и то же значение в течение недели и обновлять его по таймеру, например.

P>То есть, проблемы с вашей реализацией мы видим, а вот бенефиты какие то мифические, и вы никак не можете их описать.

Ага-ага. Бревно в глазу не замечаешь.

P>>>Дизайн вообще должен меняться постоянно, непрерывно. А вы выбираете такой подход, что изменения раз в год-два. О том я и говорю — вы пилите монолит.

P>·>Зачем?! Это ты из мира js говоришь, где каждую неделю новый фреймворк выходит и надо всё переписать? А каждые пол года очередной самый правильный дизайн появляется от фаулеров и надо срочно всю архитектуру перекраивать?!
P>Из вас технологический расизм прёт непрерывно. Извините, но на мой взгляд вместе с телепатией это признак неполной профессиональной компетенции.
P>Дизайн меняется по простой причине — требования меняются непрерывно. Вот появилось нефункциональное требование "перформанс" и мы видим, что CalendarLogic сидит в hotpath и вызывается сто раз с одним и теми же значениями. Опаньки — меняем дизайн.
Не меняем дизайн, а оптимизируем внутреннюю реализацию одного метода, ну того конкретного nextFriday(). Менять дизайн на каждый чих — признак полной профессиональной некомпетенции дизайнера решения. Разуй глаза — в сигнатуре "LocalDate nextFriday()" — совершенно не говорится что откуда должно браться — считаться на лету, из кеша, запрашиваться у пользователя или рандомом генериться от погоды на марсе.

P>Как это сделать в вашем случае — не ясно. Разве что прокинуть к вашему CalendarLogic еще и MRU, LRU кеш с конфигом "вот такие значения хранить пять минут". Вот уж простор для моков — теперь можно мокать не одно время, а целых три, а можно MRU, LRU обмокать.

Толи ты вообще в дизайн не умеешь, толи нарочно какую-то дичь сочиняешь, чтобы мне приписать для strawman fallacy.

P>>>Соответсвенно нужно выбирать такое решение, которое проще мейнтейнить, с учетом тестов.

P>·>Угу. И моки тут помогают.
P>Что бы просто так, без контекста — это слишком сильное утверждение. Но в целом оно отражает ваш подход к тестированию
Моки — это ещё один дополнительный инструмент в копилку. Больше инструментов — больще возможностей решить задачу более эффективно.

P>>>Вот код теста — как только вы его написали, решение о том, как будут протягиваться зависимости, уже принято

P>·>ТДД диктует необходимость того, чтобы код был тестируемый, а не про то куда как тянуть зависимости. Единственно что он может диктовать, что зависимостями надо управлять явно, чтобы можно было прокидывать тестовые.
P>Как только вы написали тест, то из него однозначно следует, какой будет АПИ и как будут прокидываться зависимости. Вы поймите — код теста это выражение наших намерений
P>1 апи
P>2 зависимости
P>3 поведение
P>4 связывание
P>5 входы-выходы.
P>По большому счету, идеально свести тест к п5. А вы сюда втаскиваете лошадь размером со слона.
апи диктуется требованиями клиентского кода, а не тестами. А зависимости — это уже как мы обеспечиваем работу этого апи.

P>>> 1,2,3,4,5,6 // и так на каждую строчку в табличке

P>·>Не понимаю. Ты, похоже, стиль кода путаешь с дизайном. А дизайн с управлением зависимостями. Ну сделай себе таблицу истинности и с моками, и с параметрами:
P>·>
P>·>   when(dep.getX()).thenReturn(x);
P>·>

P>Лишняя строчка
Прям ужас-ужас. Но это не главное, главное ты наконец-то таки согласился, что твои "таблицы истинности", не пострадали от моков.

P>>> Только еще вводите понятие wiring code.

P>·>Ок. Возможно я использовал неизвестную тебе терминологию. Это из мира управления зависимостей. "wiring up dependencies", "autowiring", под я под wiring code здесь имел в виду "composition root".
P>Вот-вот. Статься от 2011го, ссылается на книгу, которую начали писать в 00х на основе опыта накопленного тогда же.
И? Поэтому я и не понимаю как эта терминология мимо тебя прошла.

P>Мы только что выяснили, кто же из нас двоих вещает из нулевых

Вот от твоего любимого фаулера, свежачок: https://martinfowler.com/articles/dependency-composition.html

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

P>·>Да похрен на дизайн. Это лишь меняет место где "волшебство" происходить будет.
P>Вам похрен — это и ежу понятно. Отличия в дизайне позволяют применить более эффективные способы, например, тестирования.
Да применяй, я ж не запрещаю. Я ж говорю — похрен какой дизайн.

P>>>Прокидываете now в контролере — эту часть логично исключить из dependency injection.

P>·>В контроллере now откуда возьмётся?
P>Вы уже много раз задавали этот вопрос, и я вам много раз на него отвечал. Отмотайте да посмотрите.
Я код прошу показать, но ты упорно скрываешь.

P>>>Смысл помещения кода связывания в контроллер в том, что бы легче было интеграцию тестировать, делая самое важно явным.

P>·>Не понял, чем легче?
P>Самое важное сразу в коде метода, а не раскидано абы где по всеей иерархии dependency injection.
Субъективщина какая-то.

P>>>У вас CalendarLogic принимает зависимость к конструкторе. Технически, эта зависимость может быть и null, и предзаписаной, и привязаной к контексту — не принципиально.

P>·>Это тут причём? Технически передавая через параметр твой now — тоже может быть null и т.п. Всё то же самое.
P>Нет, не может быть. Это вам нужно делать явно в самом контроллере, который зафейлится сразу же, на первом тесте который его вызовет.
Дык я об этом и талдычу, притом не в абы каком тесте, а конкретно в тесте который тестирует этот конкретную функцию этого конкретного контроллера. А вот в моём случае это придётся делать тоже явно в коде связывания зависимостей и зафейлится если не компилятором, то во время старта, ещё до того как дело дойдёт до запуска каких-либо тестов.

P>·>В твоём же случае впихивание null будет происходить только при вызове функции приложения. Поэтому вам и требуются тесты всего уже после "успешгого" деплоя в проде.

P>Я ж вам объяснил — тесты прода нужны для самых разных кейсов.
Они не "нужны". Есть другие подходы, которые покрывают эти же кейсы (по крайней мере, подавляющее большинство), но не требуют прогона тестов.

P>·>А как ты будешь такой и-тест писать, если у тебя будет Time.Now? Что ассертить-то будешь? Как отличать два разных вызова Time.Now — клиентский от серверного? Впрочем, ты уже ответил "никак". Или я что-то пропустил?

P>Согласно бл. А вы как собираетесь отличать два разных вызова, клиентский от серверного? Вам для этого придется намастырить кучу кода с инжекцией в конструктор, только всё важное будет "где то там"
Я уже раза три рассказал. Клиентский и серверный будут иметь разный ожидаемый результат.

P>>>Вы похоже сами свой код не читали, т.к. у вас недоумение, откуда может быть null. И ежу понятно — будет передан в конструктор или явно, или неявно. Правильно это или нет — скажут тесты.

P>·>Так же можно явно или неявно передать now=null в твой nextFriday(now).
P>Теоретически — можно. Практически — первый интеграционный тест сваливается, когда вызывает этот контроллер.
Если этот тест не забудут написать, конечно. Покрытие-то ты анализировать отказываешься...

P>>>А щас вы расказываете басню, что компилер как то отличит один провайдер времени от другого времени с таким же интерфейсом

P>·>Компилятор может отличать имена символов. Это же в коде видно и на ревью всё очень очевидно — где какое поле куда передаётся.
P>Вот вы сами себе и ответили. Ну или объясните, почему код ревью перестанет работать с параметром в контроллере.
По ревью контроллера не видно какой параметр должен туда передаваться и можно ли или нельзя ли туда загонять null. Вот тут:
load: () => repo1,
load2: () => svc,

Почему один load должен быть такой, а другой сякой? А null туда можно или нет? И т.п. И это всё технические детали внутренней реализации, которые к бизнес-требованиям отношения не имеет. И это всё прод-код, покрытый какими-то тестами, которые неясно как относятся к этому коду и выполняют ли его вообще.
В моём случае:
when(timeProviders.systemClock()).thenReturn(TestData.SystemClock);

я тут явно вижу, что системные часы возвращают именно тестовое системное время.
assert someResult1.dateAAA == TestData.SystemClock.now();

Тут я уже как ревьювер могу понимать требования бизнеса могу понять, или заглянуть в джиру, чтобы проверить, что dateAAA должно соответствовать системному времени, а не времени события. И это код тестов, который описывает ожидания.

P>>>Прямой вызов — ваша функция вызовется прямо сейчас. Отложеный — потом. Например, вы сначала делаете запись, что за функция вызовется, с какими параметрами и тд, а в нужный момент сервис делает собственно сам вызов.

P>·>Какое это имеет отношение к разговору?
P>Вы же спрашиваи, что такое отложенный вызов. Если у нас нет зависимости внутри, то отложеный вызов делается как два пальца об асфальт. Это снова про гибкость, которой у вас почему то нет.
Если ты не понимаешь как это делать так спрашивай, а не фантазируй отложенно.

P>>>Просто разные аргументы.

P>·>Аргументы чего? Откуда ты знаешь, что значения этих аргументов именно Time.Now и как это проверишь в тесте?
P>С вашего аккаунта похоже разные люди пишут, да еще и меняются во время написания одного и того же сообщения. Вот некто с вашего аккаунта чуть выше ответил на ваш вопрос:
P>

P>Компилятор может отличать имена символов. Это же в коде видно и на ревью всё очень очевидно — где какое поле куда передаётся.

И как это проверишь в тесте?
И я написал почему в твоём коде это неочевидно на ревью.

P>>>·>Что синглтоны — плохо учат ещё в детском садике, ясельной группе.

P>>>Вы наверное не поняли — никаких синглтонов, только параметры.
P>·>Откуда возьмутся значения этих параметров во всех местах вызова?
P>Через параметры функции. Дизайн то плоский, а не глубокий, как у вас.
Через параметры значения передаются. Откуда они возьмутся-то?

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

P>·>Надо где-то тестировать, что ты протаскиваешь именно Time.Now а не что-то другое.
P>Вам тоже надо тестировать что вы привязали в проде пров клиенского времени, а не пров серверного или предзаписаного. Похоже, у вас там снова смена автора на вашем аккаунте.
Точка привязки будет ровно одна. К символьному имени systemClock будет привязка физического серверного источника времени. А все остальные привязки в бизнес-логике будут покрыты тестами: что ожидаемые значения в результатах связаны с тем же именем assert someResult1.dateAAA == TestData.SystemClock.now(). Если очень хочется, то и привязку физического источника времени можно будет проверить одним _системным_ тестом на всю систему, а не на каждый метод контроллера.
У тебя точек привязки — в каждом месте использования nextFriday и никак не тестируется вообще.

P>>>Интеграционным, которых ровно столько же, сколько и у вас.

P>·>Нет. У меня пирамида тестов. А у тебя — либо цилиндр, либо дырявое решето.
P>Для пирамиды нужно большое количество дешовых юнит тестов. У вас много полу-интеграционных, на моках, и мало собственно чистых юнитов. У меня — наоброт. Потому именно ваша пирамида видится мне ромбиком.
У меня "дешевые" в том смысле, что они выполняются очень быстро. Так вот даже "полу-интеграционные на моках" — выполняются очень быстро, за минуты всё. А у тебя "дешевые" — выполняются полтора часа, но называешь их "дешевыми" так просто, потому что тебе так их нравится называть, без объективных на то причин.

P>>>·>Если при этом нужно плодить копипасту, то это явно что-то не то. И неясно причём тут инлайн и гибкость кода. str.length() — это негибкий код?!

P>>>Вы сейчас перевираете.
P>·>Неперивираю, ты просто свои слова забыл. Напоминаю: " гибкость, которая проявляется в т.ч. в том, что код можно трансформировать, инлайнить". А дальше чистая логика:
P>·>"гибкий код → можно инлайнить" => "инлайнить нельзя → не гибкий код" см. https://en.wikipedia.org/wiki/Contraposition
P>С str.length всё в порядке — инлайнится с переводом в константу. У вас что то еще?
Ну зайнлайнь. Пример кода я тебе выше привёл.

P>>>Речь про инлайн вашего nextFriday(). Об ваш супер-крутой подход почему то ИДЕ спотыкается и вы проблемы в этом не видите.

P>·>Я спрашиваю "зачем инлайн"? Какую задачу-то решаем этим инлайном?!
P>Рефакторинг, оптимизации.
Рефакторинг это тоже не задача. А инлайнить для оптимизаций только самые ленивые компиляторы в режиме дебага не умеют.

P>>>Ваши тесты проверяют только тривиальные кейсы, на которых все зелено. В проде как правило кучка всего подряд.

P>·>Ну добавь эту твою кучку в тесты. Вопрос-то в чём?
P>Эта кучка называется data complexity. Я, например, билдером могу гарантировать, что данные будут запрашиваться постранично. Или что всегда будет фильтр по времени, где период не более трех месяцев.
Не очень понял, как гарантировать и что именно? И почему то же самое нельзя гарантировать другими способами? И вообще это всё больше похоже на бизнес-требования, чем на проверки каких-то кейсов в тестах.

P>Соответсвено тестами можно проверить что структура запроса та, что нам надо

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

P>>>О том, что фильтр может вернуть много записей, вам может сказать или сам запрос, или его результат.

P>·>Как запрос может что-то сказать?
P>Элементарно — всегда есть шанс, что ваш запрос вернет чего нибудь лишнее. Тестами эта задача не решается, принципиально.
P>Что бы написать корректный запрос, вам надо
P>1. гарантировать, что бы на "тех" данных вернется что надо. Это можно решить тестами — накидать данных, обработать, сравнить результат
P>2. гарантировать, что на других данных, которых у вас нет, тоже вернется ровно то, что надо. И тестами на основе данных это не решается — т.к. данных нет.
P>Итого — ваши тесты данных не являются решением, т.к. п 2 у нас обязательный. А вот дополнительные методы, например, инварианты, пред, пост условия, контроль построения запроса, код ревью основанное на понимании работы бд — это дает более-менее осязаемые данные.
Я не знаю на какой вопрос ты ответил, но я напомню что я спрашивал: "Как запрос может что-то сказать?". Контекст выделил жирным выше.

P>>>Второй вариант у вас отпадает, т.к. в тестах у вас не будет хрензнаетсколькилион записей в тестах

P>·>Зачем мне столько записей? В простом случае понадобится две записи — та которая я ожидаю, что она должна вернуться и та которая я ожидаю, что не должна вернуться.
P>Я вам выше рассказал. Нужна гарантия, что не вернется вдруг чего нибудь не то. Вариантов "не того" у вас по скромной оценке близко к бесконечности.
Как ты это предлагаешь _гарантировать_? И причём тут тесты?

P>>>Это вы собираетесь базу тестировать

P>·>Опять врёшь. И цитаты, ясен пень, не будет.
P>Вы топите за косвенные проверки запросов, тестируя связку репозиторий+бд. Вот этого оно и есть.
Я топлю за то как надо писать тесты, чтобы от них была хоть какая-то польза и поменьше вреда. А не абстрактные "проверки" которые что-то должны "гарантировать" посредством говорящей рыбы запроса.

P>>> и топите за поведение. А мне надо всего лишь построение запроса к орм проверить. Если орм не позволяет такого — надо смотреть его sql выхлоп, если и такого нету, значит придется делать так, как вы предлагаете.

P>·>"Надо" кому? Мы вроде говорим о тестировании ожиданий бизнеса. Ты правда считаешь, что пользователи ожидают определённый выхлоп sql?!
P>Пользователи ожидают, что на их данных, которых в ваших тестах нет, будет нужный результат.
Если ожидания есть, то какая проблема их добавить в тесты?

P>Вашими тестами эта задача не решается

Я показал как решение этой задачи можно тестировать моими тестами. Ты так и не рассказал как она решается вашими тестами.

P>>>И всё это нужно покрывать тестами, иначе любой дядел поломает цивилизацию

P>·>Ещё раз. У тебя в коде теста, в ожиданиях находится "in: [fn1]" и т.п. Как это соответствует "возвращать именно те данные, что прописаны в бл"? Где в бл вообще будет фигурировать in, fn1 и прочий select?
P>Не нужно сводить всё к бл напряму. У вас технологического кода в приложении примерно 99%. Он к бл относится крайне опосредовано — через связывание. А тестировать эти 99% тоже нужно.
Именно. Ты меня обвинил в том, что у меня тесты тестируют детали реализации, а на самом деле ты просто с больной головы на здоровую. Но зато у тебя дизайн самый модный!

P>>>Смотрите выше — вы фильтры предлагаете тестировать однострочными вырожденными кейсами. Как вы будете ловить те самые комбинации, не ясно.

P>·>Ну я не знаю твой код. Я разрешаю тебе писать кейсы двухстрочные и даже трёхстрочные!
P>Вы что, недогоняете, что нет возможности покрыть неизвестные данные и комбинации какими либо тестами?
Зато есть возможность покрыть известные данные и комбинации тестами. Вы и этого не делаете, а просто имена приватных функций ассертите и буковки сравниваете (тоже, кстати известные). Вот и приходится часами билдить и в проде тесты гонять чтобы хоть что-то как-то обнаружилось.

P>>>Ни то, ни другое проблему с фильтрами не найдут.

P>·>Почему?
P>Потому, что у вас нет всего множества данных, которые могут приходить от юзеров.
P>Более того — количество данных дает вам такую особенность, как слишком долгое время выполнения запроса.
P>Задача с фильтрами у меня выявила такую вещь — если данных слишком много, то запрос с секунд резко уезжает в минуты.
P>Вот оптимизацию такой вещи закрыть тестами, что бы первый же фикс не сломал всю цивилизацию
Это всё круто. Каким образом твоё "in: [fn1]" обеспечивает тебя всем множеством данных которое может приходить от юзеров?

P>>>т.е. тесты контролера зависят от мока

P>·>Т.е. в тестах контроллера мы прописали ожидание, что время используется серверное, в соответствии с бизнес-требованиям.
P>В интеграционном тесте можно сделать то же самое.
Как? Код в студию. Вот в куче кода кучи контроллеров где-то у тебя написано Time.Now. Как ты это проверишь в интеграционном тесте что некий результат содержит именно что-то вычисленное от Now, а не что-то другое?

P>>>Вторая — вызываем бл при чтении из очереди, при этом время должно быть клиенское.

P>·>Т.е. это значит, что _изменились_ бизнес-требования и теперь мы должны брать не серверное время, а клиентское. Ясен пень, это фукнциональное изменение и соответствующие тесты надо менять. Если у тебя ни один тест не упадёт, у меня для тебя плохие новости.
P>Это как раз нефункциональное требование. Например, у нас запрос выполнялся слишком долго, и мы процессинг вытолкнули в очередь с отложеным процессингом.
Не это, а то что вначале мы использовали серверное время, а потом вдруг стали использовать клиентское — это изменение функциональное, т.к. источники времени — принципиально разные и влияют на наблюдаемый результат. Если бы было время клиентское изначально, то никаке функциональные тесты не пострадают.

P>>>А это значит, что ваши тесты контролера идут в топку, как и моки которые вы под это дело прикрутили.

P>·>В контроллере надо будет заменить использование systemClock() на eventTimestampClock() и поправить тесты, чтобы зафиксировать функциональное изменение.
P>Функционых изменений здесь ровно 0. С т.з. пользователя никаких функций не добавляется.
Меняется наблюдаемое поведение и вообще может быть дырой в безопасности. Клиентское время, скажем, можно захакать.
А если в твоём случае systemClock() и eventTimestampClock() — вещи функционально неразличимые, то вообще неясно зачем ты тут с меня трясёшь как я их буду различать.

P>>>зачем?

P>·>Чтобы понять какое отношение имеет метапрограммирование к тестированию ожиданий бизнес-требований.
P>Метапрограммирование дает возможность зафиксировать свойства запроса, которые вы с вашими тестами сделать не сможете, никоим образом.
А ещё я кофе моими тестами сварить не смогу. Что сказать-то хотел?
Напомню, мы сравниваем тестирование ожиданий бизнес-требований твоими тестами с "in: [fn1]" vs моими тестами c "save/find". Так причём тут метапрограммирование?

P>>>Очевидно, посыплются не все. Ну нет такой ситуации что бы билдер на каждый запрос лепил UNION. Отвалится скорее всего несколько кейсов, ради которых это все и затевается.

P>·>Ну этот кусочек может быть в большой группе запросов, выбирающих разные данные по-разному, но часть логики — общая.
P>Может. Это дополнительные гарантии, сверх тех, что вы можете получить прогоном на тестовых данных. Потому издержки обоснованы.
Гы. Опять у тебя тесты гарантии дают.

P>>>Кто даст гарантию, что в проде вы правильно прописали зависимости для CalendarLogic? Интерфейс то один и то же

P>·>Гарантию, конечно, никто не даст, но покрыть тестами с моками тоже можно. Для этого моки и придумали.
P>Вот вот — прибиваете контроллер к тестам на моках.
Ты так говоришь, как будто это что-то плохое.

P>>>Буквально curl тест все что может, это сравнить выхлоп, и всё. Если там время не фигурирует, то никак.

P>·>Ну, допустим, фигурирует. И чем это тебе поможет?
P>Тогда можно сравнить выхлоп, тот или не тот.
Так я тебя и спрашиваю как ты будешь сравнивать выхлоп зависящий от Time.Now — тот он или не тот?

P>>>Если же вы имеете ввиду интеграционные, то будет все ровно как и у вас.

P>·>С моками? Ну дык об чём спор-то тогда? Ведь именно моком я могу протащить тестовые значения now в нужные места. Там, где ты предлагаешь Time.Now синглтон, в который ты ничего протащить не сможешь.
P>Этим протаскиванием вы бетонируете весь дизайн. Смена дизайна = переписывание тестов.
Нет.

P>>>Бинго! Оптимизация, нефункциональные требования, технические особенности — все это имеет чудовищное влияние, намного больше, чем сама бизнес-логика.

P>>>Если вы потеряете оптимизацию — приплызд. и тест нужен зафиксировать эту особенность что бы сохранить при изменениях.
P>·>Это уже будет перф-тест и к обсуждаемой теме не относится.
P>Покрыть перформанс тестами все вырожденные кейсы вам время жизни вселенной не позволит. Некоторые детали реализации нужно фиксировать обычыными тестами.
Обычные тесты не могут тестировать перф. А детали реализации лучше не тестировать.

P>>>См выше — все нефунциональные вещи нужно проверять и фиксировать тестами.

P>·>Это не оправдывает твой подход, что функциональные тесты должны тестировать метапрограммирование.
P>Метапрограммирование тестируется там, где оно применяется в силу естественных причин. Если у вас запросы тривиальные, вам такое не нужно.
Ты не понял. Надо тестировать не метапрограммирование, а ожидания.

P>>>Покажите, как вы все эти кейсы обнаруживать будете.

P>·>Возвращаемся к старому спору. Я уже отвечал на это неоднократно: системы мониторинга, health probes и прочие операционные инструменты. Это не тестирование.
P>Неэффективно. Для многих приложений час невалидной работы может стоит любых денег. Дешевле запустить тесты на проде — час-два и у вас информация по всем функциям.
Ну никто нам не позволит делать "тестовые" сделки на прод системах. Каждая сделка — это реальные деньги и реальная отчётность перед регуляторами. Так что "информацию по всем функциям" нам приходится выдавать ещё до аппрува релиза.

P>>>Полагаю ответ будет "у нас багов на проде нету" ?

P>·>Вот таких багов "протух-обновился сертификат" — и не должно быть. Такие вещи именно мониторингом отслеживаются заранее, чтобы пофиксить можно было до возникновения этой ситуации. А у вас серты протухают ВНЕЗАПНО, и обнаруживаются только тестами постфактум, что у вас в проде что-то где-то отвалилось... ну могу только пособолезновать вашим клиентам.
P>Вы снова включили телепатию. Сертификат может обновиться не у вашего приложения, а у какого нибудь из внешних сервисов. Мониторинг вам скажет, когда N юзеров напорются на это и у вас начнет расти соответсвующая метрика.
Нет, мониторинг нам скажет, что сессия с таким-то сервисом сломалась и свистайте всех на верх.
Такое — major incident и соответствующий разбор полётов с пересмотром всех процессов — как такое допустили и определение действий, чтобы такое больше не произошло.

P>>>насколько я понимаю, это я все еще жду вашего примера про фильтры, который решает ту самую data complexity

P>·>Какую ту самую? Я никакой "data complexity" не видел и телепатией не обладаю, чтобы выдать какое-то решение для задачи о которой я не слышал.
P>У вас есть иллюзия, что ваши тесты гарантируют отсутствие лишнего выхлопа на данных, которые у вас отсутсвуют.
У меня такой иллюзии нет, ты опять насочинял, я такое нигде не писал. О гарантиях в тестах фантазировать любишь только ты.

P>>> Вход на фильтры полным перебором подавать или магией воздействовать?

P>·>Да так же, как у тебя. Только ассертить не буквальный текст запроса, а результат его выполнения
P>
Угу, вот так вот просто.

P>>>Вы когда нибудь видели дашборды где фильтры можно выбирать хрензнаетсколькилионом кнопочек и полей ввода?

P>·>Насколько мне доводилось видеть, они обычно через & соединяются. Т.е. список независимых фильтров. Сложность возникает когда фильтры между собой взаимосвязаны, но обычно такое избегают, т.к. юзерам такая compexity тоже не под силу, и использовать нормально всё равно не получается.
P>Ну да, вы сами чего то додумали, и воюеете за это
Потому что ты никогда задачу не обозначил. Задаёшь какие-то наводящие вопросы.

P>>>Не просто манифест — а любое описание, абы ваши пиры могли его понять и цикл разработки был быстрым.

P>·>Что значит за любое описание? Манифест это и есть описание api.
P>Любой формат, любое расширение файла У ваших пиров должен быть инструмент для использования такого описания.
P>Например, вы решили выдать swagger, но описали в ём такое апи, которое ни один генератор кода не понимает. Это значит, что вы АПИ вообще не предоставили — мало кто в своём уме будет вручную имплементать нюансы которые к релизу поменяются 100500 раз
Ну я хотя бы могу начать допиливать этот самый swagger, чтобы генератор таки стал понимать.
А у тебя всё гораздо хуже. Ты пишешь аннотации, конфиги, тенантов, реализацию, веб-сервер, потом у тебя наконец-то http-запрос выдаётся этот же самый swagger по этим аннотациям, который ВНЕЗАПНО ни один генератор кода не понимает. И это реальная ситуация, которую помнится уже обсуждали пару лет назад и твоим решением было "ну пусть клиенты пишут всё ручками", т.к. перепилить свои аннотации и http-запрос ты уже не сможешь, ибо проще пристрелить.

P>>>Появятся после того, как вы заделивирите АПИ, например спустя год, когда у вас уже версия 1.x. И вот здесь вы решите перейти на rust, то в этот момент крайне странно топить за design-first — у нас уже готовое приложение версии 1.x, и нужно сделать так, что бы манифест отрендерился — с учетом тенантов и прочих вещей, включая not implemented. Или вы собираетесь бегать по отделам и рассказывать "а чо вы это делаете — мы же на джаве год назад это решили не делать" ?

P>·>У вас уже есть имплементация и мы вот наконец спустя год выкатываем манифест api, но ты всё ещё это называешь design-first?! Нет, это типичный code-first — когда прыгать надо, думать некогда.
P>Вы похоже не читаете. Какой смысл вам что либо рассказывать?
P>1. когда стартуем проект, где центральная часть это АПИ. Идем через design-first, формат файла и расширение — любое, см. выше
P>2. готовый проект — уже поздно топить за design-first, просто генерим артефакты
P>3. переписываем проект на rust — снова, нам design-first не нужен, это вариация предыдущего кейса
Не понял, как из 1 получилось 2?

P>Вот если у вас переписывание случается каждый день, или даже чаще, да на новый язык, то АПИ нужно держать в каком то универсальном формате.

P>Правда, в этом случае я более чем уверен, вы дольше всего будете подбирать генераторы кода под это АПИ, или выписывать все загогулины руками с воплями "я уже заимплементал херли вы перенесли параметр из квери в хидеры!!!!!!!11111йййкукуку"
Ну я о чём и говорю. У вас "думать некогда, прыгать надо". Пишете аннотации, код, выкатываетет, потом как всегда ВНЕЗАПНО "поздно топить за design-first"

P>>>Я вам говорю откуда возьмется апи в готовом, если вы решите уже готовое переписать на другом языке.

P>·>Причём тут design-first?
P>У вас точно меняется контингент, или в голове, или за компом:
P>

P>Если вы захотите переписать свой сервис на другой ЯП, ну там с js на rust — что вы будете делать со своими аннотациями?

P>Переписывание предполагает, что у нас уже есть и АПИ, и реализация этого АПИ. Отсюда ясно, что проще сгенерировать артефакт, чем изначально держаться за конкретный формат и топить за design-first
Ага, верно, проще прыгать чем думать. Не спорю.