Re[43]: Что такое Dependency Rejection
От: Pauel Беларусь http://blogs.rsdn.org/ikemefula
Дата: 13.01.24 18:52
Оценка:
Здравствуйте, ·, Вы писали:

P>>1. На все — все ваши тесты неявно теперь зависят от мока. И если вы закостылили мок, то заточили тесты под синтетику, код — под такие тесты.

P>>2. Если по моку на каждый — можно хоть ревью сделать. Кода больше, но и контроля больше.
·>Я не понимаю что такое заточка мока. Код с моками это просто код. Там где код общий, там он переиспользуется из разных мест. Там где не общий, там не переиспользуется. Мок это просто механизм передачи данных. Ровно такой же вопрос я могу спросить про твои параметры.

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

P>>Проверка склейки — такого не будет. А вот поддерживает ли приложение такую функцию — нужно проверять интеграционным тестым.

·>Не всегда нужно, в этом и суть. Интеграционный тест проверяет интеграцию, что компоненты работают вместе. А не каждую функцию приложения.

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

P>>И ваши моки не избавляют этого.

·>Моки не избавляют. А помогают избавляться.

Нисколько. Моки прибивают вас к конкретному дизайну — см пример про инлайн кода. Этот пример демонстрирует особенность — даже ИДЕ перестает помогать.
А вот если вы отказываетесь от моков, соотвественно, ИДЕ вам в помощник — вдруг рефакторинг inline начинает работать сам собой

P>>Вариантов дизайна всегда больше одного

·>Именно. Это всё субъективщина. Эти варианты у тебя меняются каждые год два, как женские шляпки. А вот требование ресурсов, использование сети-тредов-диска — это объективные критерии. На них я и ориентируюсь, а не на последние веяния моды.

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

P>>Есть же пример Буравчика. Вам мало, надо к абсурду свести?

·>В смысле ты хочешь сказать, что его пример на хаскель не переписывается? Переписыватеся, с монадами и т.п. Но проще он не станет. Поэтому вопрос и остаётся — накой. Твои предложения "улучшить" я не понял в чём же улучшение.

Я ж объяснил — таблица истинности рядом с тестом и кодом, моки-стабы тоньше некуда. Вы рассматриваете систему в отрыве от тестов. Это категрически неверно. Даже конкретный деплоймент не имеет смысла без тестов — чемодан без ручки. Нужно проверить, а все ли путём, без тестов это никак. Более того — поменялись внешние зависимости — снова нужны тесты.
Соответсвенно нужно выбирать такое решение, которое проще мейнтейнить, с учетом тестов.

P>>В стиле ТДД — написали тесты на моках, а потом и дизайн вышел соответствующим

·>Нет.

У вас похоже особое видение каждого термина, который мы обсуждаем. ТДД по Кенту Беку целиком про дизайн.
Вот код теста — как только вы его написали, решение о том, как будут протягиваться зависимости, уже принято
const calendar = new CalendarLogic(Predefined.Now);

expect(nextFriday()).to.eq(Predefined.Friday);


P>>·>Ну "правильного" дизайна ты предолжить просто не смог. Т.к. это более менее реальный проект с нетривиальной бизнес-логикой. А твои штуки вменяемо работают только на хоумпейджах.

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

@test()
@params(
    1,2,3,4,5,6  // и так на каждую строчку в табличке
)
@params(
    8,4,2,1,3,5 // точно так же и исключения прокидывают
)
...   сколько строчек, столько и тестов фактически выполнится
"a cool name test name" (v1, v2, v3, repo1, svc, repo2, result) {
   let logic = Logic(v1,v2,v3);

   let r = logic.run({
      load: () => repo1,
      load2: () => svc,
      ... и так прокидываем все что надо
   }) 

   expect(r).to.deep.eq(result)
}


P>>Связывание и есть wiring код. Основная обязанность контроллера — именно это. И тестировать связку, как следствие.

·>Нет, обязанность контрллера связывать входящие запросы с бизнес-логикой. wiring code — это отдельный код для связывания компонент друг с другом.u

Вы сейчас в слова играете. Сами же пишете — "обязанность контроллера связывать" Только еще вводите понятие wiring code.
Часть кода связывания будет вне контролера, и правила вы водите сами, это и есть тот дизайн. Прокидываете через dependency injection — значит внутри контролера этого не будет.
Прокидываете now в контролере — эту часть логично исключить из dependency injection.

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

P>>·>Что за ерунда? Откуда там null возьмётся? Что за "повесите на dependency injection"? источник времени — это зависимость CalendarLogic, инжектится через конструктор. ЧПФ, забыл опять?

P>>Сами спросили — сами ответили.
·>Откуда _у тебя_ null берётся? У меня никаких null тут нет.

У вас CalendarLogic принимает зависимость к конструкторе. Технически, эта зависимость может быть и null, и предзаписаной, и привязаной к контексту — не принципиально. Ваша задача — сделать так, что бы в контролер этот CalendarLogic пришел с нужной завимостью, а не произвольной. Соответсвенно, гарантии фиксируем через интеграционный тест — конкретный use case собран как положено. И вот здесь внутренности реализации вообще роли не играют — проверяется интеграция, а не внутреннее устройство.

P>>О — InstantSource появился. Значит инжектиться может не один единственный вариант с Now, а минимум два.

·>А ты вообще код мой читал? В первой строчке код-сниппета
Автор: ·
Дата: 28.12.23
. Ты по-моему сам со своими фантазиями споришь, я тебе только мешаю.


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

P>>Где у вас тестирование инжекции? А ну как на проде все инжектиться будет по старому?

·>В прод-classpath не видны моки и вообще вся тестовая белиберда. Код не скомпилится просто, если туда правильную зависимость не вписать.

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

P>>А как вы понимаете, что такое отложеный вызов? Недавно вам непонятно было что такое dependency injection, потом выяснилось, что все вам известно.

·>Мне не было понятно, что ты под этим понимаешь.

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

P>>Вот-вот. Как вы отличите что первый и второй не перепутаны? Это ж ваша любимая тема — надо проверить, что передали то самое!

·>Устанавливаем в тестах, что "у нас на сервере now это 21:00:01, а на клиенте "21:00:02". И в тестах можно ассертить что в нужных случаях ожидаемое время. Что ты будешь делать со своим синлтоном Time.Now — ты так и не рассказал. Вообще я не понимаю как ты такое можешь использовать и тем более тестировать, просвети.

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

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


Вы наверное не поняли — никаких синглтонов, только параметры.

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

P>>А еще можно обойтись без моков И свести к таблице истинности, поскольку интеграционный тест всё равно понадобится.
·>Ты заявил, что никак не сможешь время протестировать.

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

P>>·>Да, но кода этого много накопипасчено.

P>>Ну и что?
·>Что каждую копию надо покрывать тестом. Ты правда сейчас собираешься копи-пасту продвигать как правильный дизайн?

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

P>>Затем, что признак хорошего кода это его гибкость, которая проявляется в т.ч. в том, что код можно трансформировать, инлайнить, итд, да хоть вовсе от него избавиться.

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

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

P>>Вы так и не рассказали, как будете решать ту самую задачу с фильтрами.

·>Рассказал и даже код показал,
Автор: ·
Дата: 28.12.23
насколько понял твою задачу. Если ещё остались вопросы, спрашивай.


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

P>>·>Дело в том, что в случае субд у тебя кода субд нет.

P>>И не надо.
·>Надо, если хотим тестировать поведение, а не детали имплементации.

Тестируйте базу, я ж не мешаю. Можете еще и операционку и джава рантайм сюда добавить

P>>Билдера запросов к базе данных. Вы что думаете, все sql запросы руками пишутся?

·>Да похрен как они пишутся, хоть левой пяткой. Это всё детали реализации. Главное что они работают ожидаемым способом.

Работают ожидаемым способом — это вы на примитивных фильтрах, что вы продемонстрировали, не проверите. О том, что фильтр может вернуть много записей, вам может сказать или сам запрос, или его результат.
Второй вариант у вас отпадает, т.к. в тестах у вас не будет хрензнаетсколькилион записей в тестах

P>> Кое что, для аналитики — возможно. Но это большей частью всё равно перегоняется в орм или билдер. Есть орм — можно проверять построение логики запросов орм.

·>В смысле ты в своих тестах linq/etc тестируешь? Зачем? У linq свои тесты есть.

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

P>>Запрос к базе должен соответсвовать бизнес-требованиям. Только проверяем мы это в т.ч. посредством метапрограммирования.

·>Что за бред? У тебя в FRD прописаны тексты запросов??! Прям где-то у вас есть бизнес-требование "select * from users where id=?" и "in: [fn1]"?! Приходит к вам клиент и говорит "а мне очень нужно чтобы out: [fn2], когда можете сделать?"

Что вас смущает? Запрос к бд должен возвращать именно те данные, что прописаны в бл или непосредственно следуют из бл, нефункциональных требований итд. Вас почему то тянет всё к абсурду сводить.
Кроме бизнес требований, у нас куча важных вещей — те самые нефункциональные. Их на порядок больше чем бизнес-требований.
И всё это нужно покрывать тестами, иначе любой дядел поломает цивилизацию

P>>·>Цитату в студию или признавайся, что опять соврал.

·>Цитаты нет. Т.е. соврал.

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

P>>Вы до сих пор не привели решение кроме "ищем в бд"

·>Я код
Автор: ·
Дата: 28.12.23
привёл. Что не устраивает-то??! "ищем" — это "find", английский язык знаешь?


Смотрите сами: assertThat(testSubject.find(new Filter.byEqual("Vasya"))).isPresent();
У вас testSubject это или репозиторий, который вы предлагаете с подключеной бд тестировать, или компонент, который дергает мок репозитория
Ни то, ни другое проблему с фильтрами не найдут.

P>>>>Смотрите про тривиальный перенос вызова из одного места в другое — ваш nextFriday поломал вообще всё

P>>·>Куда смотреть? Поломал что?
P>>Вам надо мок подфиксить, что бы время было той системы
·>Чё?

Смотрите — первая версия — вызываем бл прямо из контролера.
Ваши тесты — вы обмокали провайдер времени, передаете туда-сюда, включая тесты контролера
т.е. тесты контролера зависят от мока

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

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

·>Покажи пример бизнес-требования.

зачем?

P>>и у вас кейс с вырожденным фильром до сих пор не покрыт тестом.

·>Я не видел этого кейса, и не могу нафантазировать какие у тебя возникли проблемы его покрыть.

А он и не виден, т.к. проблема тупо в количестве вариантов которые могут порождать фильтры. Та самая data complexity.

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

·>Да причём тут стиль? Это мелкий чих. Я могу переписать например "SELECT ... X OR Y" на "UNION SELECT ... X ... SELECT ... Y" для оптитмизации. У тебя все тесты посыпятся и любая ошибка — увидишь после деплоя и прогона многочасовых тестов, в лучшем случае (а скорее всего только в проде). А у меня только те тесты грохнутся, если я реально ошибусь и что-то поломаю в поведении, ещё до коммита.

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

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

·>Зато этот код очень простой, линейный и на 99% покрывается компилятором.

Не проверяется. Ваш CalendarLogic может принимать разные экземпляры — с текущим временем, клиентским, серверным, из параметра, итд. Всё что вам компилятор скажет, что типы нужные.
Ну и что?
Кто даст гарантию, что в проде вы правильно прописали зависимости для CalendarLogic? Интерфейс то один и то же

> Остальной 1% покрывается стартом приложения.


Еще меньшая гарантия.

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

·>Я спросил как сделать так, чтобы такие тесты были, ты ответил
Автор: Pauel
Дата: 31.12.23
"Никак.". Т.е. тестов у вас таких просто нет.


Вы вопрос свой прочитать в состоянии? как именно ты собираешься в "curl" и-тестах ассертить
Буквально curl тест все что может, это сравнить выхлоп, и всё. Если там время не фигурирует, то никак.
Если же вы имеете ввиду интеграционные, то будет все ровно как и у вас.

·>Я о чём и говорю. В каких-то случаях у тебя могут меняться запросы. Ты уже сам говорил о случае с пустым значением, которое ты заявил, что и-тестами тестировать не собираешься.

·>И это могут быть нефункиональные требования, а какие-то технические детали, оптимизации. Простой пример. Скажем, если идёт выборка по небольшому списку параметров, то строится запрос с "IN (?,?)", иначе создаётся временная табличка и с ней джойнится.

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

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

·>Именно. Поэтому надо их выполнять и проверять результаты, неважно мета там или фета. Никто вам ничего не запрещает, а мой поинт в том, что важно что запросы делают, а не какие инструменты используются.

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

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

·>Ну вроде это уже вс проходили и забили. Заталкивать тесты в прод-код... зачем??!

Затем, что это быстрый, дешовый и надежный способ узнавать о проблемах с приложением в целом
1. поменялись зависимости на той стороне — убедились, что все в порядке, или сообщили о проблеме, вкатили хотфикс
2. поменялась инфраструктура — убедились, что все в порядке
3. протух-обновился сертификат — оно же
4. обновили часть системы — оно же, тестируем всю
5. высокая нагрузка — снимаем наши собственные значения, получаем ту инфу которую другим способом никак не получить. Например — некоторые юзеры под высокой нагрузкой получают некорретные данные.

Покажите, как вы все эти кейсы обнаруживать будете. Полагаю ответ будет "у нас багов на проде нету" ?


P>>>>И как же вы решаете проблемы с data complexity которая тестами не решается используя исключительно косвенные тесты?

P>>·>А конкретнее? С примером кода.
·>Именно. Примера кода я так и не дождусь.

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

P>>Это и есть тесты билдера. Только конкретного, в котором заложено много различных вырожденных кейсов. linq не в курсе, что вы вытаскиваете всю базу, ему по барабану. Мне — нет.

·>Linq выдаёт список результатов — вот и проверяй, что в этом списке — вся база или не вся.

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

P>>Ну так найдите эту комбинацию. А их, мягко говоря, немало. Как будете искать?

·>Анализом требовний, кода, данных, етс. Обычный corner case analysis. А как вы будете искать?

Вот-вот. Анализом. Только результаты нужно зафиксировать тестами таким образом, что бы следующиее изменение не сломало всё подряд втихую.

P>>И вы так прямо и втащите coq в любом проект на джаве?

·>Нет. А вы таки втащили доказательное программированипе в js?

Зачем? Доказывать свойства можно и без coq, только результаты нужно выражать в дизайне и подкреплять тестами.

P>>Еще раз — фильтр не хардкодится, а строится.

·>Я потерял суть. О чём речь вообще? Пример в студи. КОД!

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

P>>Нет, не называется. Разница между code-first и design-first не формате и расширении файла, как вам кажется, а в очередности деливери.

·>Верно. В design-first деливерится манифест first.

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

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

·>Ну да. Откуда же берутся конфиги/тенанты/http-запросы?

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

P>>·>Ты опять врёшь. Не телепатией, а из твоих слов: "Манифест генерируется по запросу, обычно http", противоречие выделил жирным. Или ты похоже просто в прыжке переобуваешься.

P>>Вы вместо уточнения пытаетесь в телепатию играть. Если у нас готовое приложение, то конечно же генерируется — и странно было бы иначе делать.
·>Угу. ЧТД. "готовое приложение" == "code-first".

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