Re[40]: Что такое Dependency Rejection
От: · Великобритания  
Дата: 06.01.24 21:37
Оценка:
Здравствуйте, Pauel, Вы писали:

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

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

P>>>Нету такой функции как склейка. Вы все еще думаете вайтбоксом.

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

P>>>Тесты на моках интеграцию не тестируют, и не могут. Тест интеграции — проверка функционирования в сборе, а не с отрезаными зависимостями.

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

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

P>·>Моки позволяют упростить дизайн, снизить тяжеловесность тестов, уменьшить количество интеграционного кода, и соответственно — меньше кода — меньше тестов.
P>Наоборот — моки это инструмент тестирования тяжелого, монолитного кода перемешанного с зависимостями. Например, когда физически невозможно выделить логику в простые вычисления.
Что за "физически"? Тезис Чёрча знаешь? Любой код можно переписать на чистый, хаскель неплохо это доказывает. Вопрос — накой?!

P> Тогда приходят на помощь моки — как вы сами и сказали, замокали один компонент, что бы проверить остальные два.

Замокали "плохой", тяжёлый компонент, требующий файловой системы, сети, многопоточки, чтобы хорошо и легко проверить остальные два на саму логику и бизнес-требования.

P>Или как у Буравчика — мутабельные вычисления, кучка зависимостей да два ифа.

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

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

Я не знаю что за такое "тяжелый дизайн".

P>>>Куда ни ткни у вас "таких проблем у меня не может быть".

P>·>Потому что для данной конкретной проблемы существует подход, чтобы эта проблема не возникала. Если ты не хочешь использовать этот подход, тебе приходится бороться с этой проблемой.
P>Я вам вобщем то ровно то же говорю.
И? Что такого, что я знаю как некоторые проблемы решаются? Магией для тебя что-ли выглядит?

P>>>Нужно проверять все эти call site вне зависимости от способа доставки депенденсов, каждая функция должна быть вызвана в тестах минимум один раз.

P>·>Проверять на что и каким тестом? В call site я могу использовать мок nextFriday() и проверять поведение ю-тестами.
P>Каким угодно, абы гарантии были, и тестирование было дешовым. Если мы выталкиваем связывание наверх, в контролер, очевидно, что юнит-тесты будет писать легче. В противном случае такой дизайн применять не стоит. И ежу понятно.
Связывание находится в wiring code. Контроллер это просто ещё один компонент, ничем принципиальным от других не отличается.

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

P>>>Будь у вас фукнциональный язык, можно было бы типизировать чуть не все подряд, но у вас такого фокуса нет.
P>·>Не очень понял. Наверное мы говорим о разных вещах. Покажи код где там какой типизации не хватает в java.
P>Джава умеет пересечения, объединения типов, условную типизацию, алгебраические типы?
P>Ы-ы-ы — всё это придется затыкать юнит-тестами, компилер вам не помощник.
Покажи код где там какой типизации не хватает в java. И не просто общие слова, а не забывай про контекст обсуждения.

P>·>Так я это и тестирую результат, что он зависит от nextFriday() правильным способом. Разговор о другом.

P>·>nextFriday(now) — это функция возвращающая пятницу для данного момента времени. А nextFriday() — это функция возвращающая пятницу для текущего момента времени. В первом случае мы по месту использования должны откуда-то брать некий момент времени, а для второго — мы знаем, что это текущий момент.
P>·>В функциональных яп — это частичное применение функции. У нас просто разная семантика функции "текущее время" vs "данный момент времени". Причём тут white/black — вообще неясно.
P>whitebox — это ваше желание протестировать каждый вызов функции.
P>blackbox — тестируем исходя из функциональных требований.
И? "текущее время" vs "данный момент времени" — это функциональные требования. Причём тут вызовы функций?

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

P>·>Не нужно его протаскивать. Он уже "частично применён" в CalendarLogic. Ты вообще понимаешь что такое ЧПФ?!
P>о
P>Вы сейчас в слова играете. Если не протаскивать зависимости, в CalendarLogic будет null во время вызова, буквально. Это значит, вы всю мелочевку повесите на dependency injection.
Что за ерунда? Откуда там null возьмётся? Что за "повесите на dependency injection"? источник времени — это зависимость CalendarLogic, инжектится через конструктор. ЧПФ, забыл опять?

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

Поменяется InstantSource, будет добывать время не из system clock, а из таймстампа в очереди. Это вообще никакое не архитектурное изменение, а одна строчка в wiring code — что инжектить как источник времени.

P>И теперь ваша стройная система моков ляснулась.

Опять страшилки какие-то. В каком месте что ляснулось?

P>В моем случае всех изменений — вместо прямого вызова делаем отложеный.

Что за отложенный вызов? Вызов где?

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

Ну будет два InstantSource в разных местах.

P>Это вам пример, как изменение дизайна вносит хаос именно в моковую часть тестов.

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

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

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

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

P>Ровно там, где у нас связывание, а таких мест
P>1 немного
У тебя много, на каждый сценарий.

P>2 код в них тривиальный, линейный

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

P>>>Если вы не согласны — покажите как ваша иде инлайнит код nextFriday по месту каждого из call site. Явите же чудо, про которое вещаете тут годами

P>·>А ты можешь заинлайнить, например, хотя бы str.length()? Или тоже "тапочки не приносит"?
P>Вы только что говорили о чудесной поддержке иде именно вашего дизайна. Покажите это чудо. Мне вот ИДЕ спокойно может заинлайнить любое из мест вызова nextFriday(now)
Круто, но зачем? Задачу-то какую решаем?

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

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

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

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

P>У вас будет ровно то же — только косвенные проверки и больше кода.

Я код уже показывал, не фантазируй.

P>>>Еще раз — у меня нет буквально в коде того или иного запроса целиком, а есть конструирование его.

P>·>Цитирую тебя:
P>·>

P>·>Что вас смущает? deep.eq проверит всё, что надо. query это не строка, объект с конкретной структурой, чтото навроде
P>·> sql: 'select * from users where id=?',
P>·>

P>·>Это не буквально?!
P>Это выхлоп билдера. С чего вы взяли, что такой код надо руками выписывать?
Это какой-то небуквальный выхлоп или что? Выхлоп какого билдера?
Тесты обычно пишут люди для людей. А вы генерите тесты?!

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

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

P>Ваша альтернатива — забить на data complexity, так себе идея.

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

P>>>Даже если вы вообще удалите зависимость от Time.now, это ничего не изменит — количество интеграционных тестов останется прежним

P>·>А у меня этих твоих do_logic — по нулю на юз-кейс. Чуешь разницу?
P>do_logic и есть или юз-кейс, или контроллер.
  Я имею в виду вот этот твой do_logic. Это не юзкейс. Это код-лапша.
def do_logic(v1,v2,v3):
  l = Logic(v1,v2,v3)
  p = l.run(
    load = ..., repo  // здесь и ниже хватит простой лямбды
    load2 = ...., svc
    commit = ..., repo+kafka // как вариант, для кафки можно протащить доп. имя, например event
  );

  Return serialize(p)


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

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

P>Вы вот предпочитаете забить на data complexity, но продолжаете отрицать это

Ты врёшь опять. Цитаты ведь не будет.

P>>>Именно что следует. Вы топите за вайтбокс тесты, а у них известное свойство — поломки при изменении внутреннего дизайна.

P>·>Я топлю за вайтбокс в том смысле, что тест-кейсы пишутся со знанием что происходит внутри, чтобы понимать какие могут быть corner-cases. Но сами входные данные и ассерты теста должны соответствовать бизнес-требованиям (для данного id возвращается правильно сконструированный User). Следовательно, изменение имплементации всё ещё должно сохранять тесты зелёными.
P>Нет, не должно.
Должно, т.к. это тестирование бизнес-требования.

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

Куда смотреть? Поломал что?

P>·>Вот твой код "{sql: 'select * from users where id=?', params: [id], transformations: {in: [fn1],out: [fn2]}" — таким свойством не обладает, т.к. проверяет детали реализации (текст запроса, порядок ?-парамов, имена приватных функций), а не поведение. Этот тест будет ломаться от каждого чиха.

P>Кое что сломается. Поскольку это метапрограммирование, то это ожидаемый результат.
Ожидаемый кем/зачем?

P>Что бы от каждого чиха — такого нету. Запросы меняются с изменением бизнес логики.

Я тебе приводил пример — заменить звёздочку на список полей — это чих, а не изменение бизнес логики.

P>>>Выглядит так, что вы отрицаете недостатки моков и вайтбокс тестов, даже посредтством такой вот дешовой детской подтасовки.

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

P>>>Проверять нужно не точки сборки, а всю сборку целиком. Точки сборки — та самая вайтбоксовость с её недостатками, которые вы отрицаете уже минимум несколько лет

P>·>Ок, переформулирую. В сборке целиком тебе нужно проверять все точки . Тебе надо будет проверить, что где-то в твоём склеивающем коде do_logic_742 в качестве now в nextFriday(now) подставилось время сервера, а не клиента, например. И так для каждого do_logic_n, где используется nextFriday.
P>В интеграционных тестах мы не проверяем точки — мы проверяем саму сборку.
Если сборка идёт в трёх точках — проверить легко. Если сборка идёт в трёхсот точках — проверить сложно.

P>То, что вы пишете — именно вайтбокс тест, тавтологичный "проверим что подставилось время клиента" — это и есть суть проблемы.

Почему вайтбокс? Тест бизнес-требования — "в ответе приходит правильное время".

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

Как ты _проверишь_, что результат тот же и соответствует ожиданиям?

P>>>Функционирование и работоспособность это слова-синонимы. В руссокм языке это уже давно изобретено.

P>·>Тогда неясно зачем тебе и юнит тесты, и интеграционные, если и те, и те проверяют функционирование, и что тебе в твоих и-тестах приходится проверять всю функциональность.
P>Функции приложения никакие юнит-тесты не проверяют. Функции компонента никакие юнит-тесты не проверяют. А вот вычисление nextFriday — ровно то, что нужно .
Я не знаю что ты сейчас такое написал и к чему. Напомню, тут речь идёт о:

·>Как ты тестируешь что построенный запрос хотя бы синтаксически корректен?
тот самый интеграционный тест, на некорретном запросе он упадет

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

P>>>Например, те самые требования. Если нам вернуть нужно User, то очевидно, что fn2 должна возвращать User, а не абы что.

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

P>>>Вы снова додумываете вместо того, что бы задать простой вопрос.

P>·>Вопрос о чём? Ты сам сказал, что fn2 видно только для тестов. Цитата: "fn1 и fn2 должны быть видны в тестах, а не публично". Я просто пытаюсь донести мысль, что это code smells, т.к. тесты завязаны на непубличный контракт компонента, иными словами, на внутренние детали реализации.
P>Для юнит-тестов это как раз нормально, их предназначение — тестировать всю мелочевку, из за которой у вас вообще что угодно можно происходить
Дело не в мелочёвке-большичёвке, а в том, что ты тестируешь код как написано, а не ожидания/бизнес-требования. Ровно то, чем ты меня пытался попрекать.

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

Мрак.

P>>>Ну как же — вы собирались фильтры тестировать посредством выполнения запросов на бд, те самые "репозитории" которые вы тестируете косвенно, через БД.

P>·>Да.
P>Вот вот.
Ты так говоришь, как будто это что-то плохое.

P>>>Или у вас фильтрация вне репозитория сделана?

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

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

P>·>С т.з. тестов репы нет никакого "содержимого тестовой бд". "сохранить данные в бд" == вызов метода save репы, "поискать их разными фильтрами" == вызов метода find. Я же в пример кода уже дважы тыкал. С т.з. теста впрочем и бд как таковой тоже нет, можно сказать, есть просто класс с пачкой методов, которые тест дёргает и ассертит результаты.
P>Для простых кейсов сгодится.
А сложные будем упрощать. Или у тебя есть конкретный пример когда не сгодится?

P>>>А вот подробности того, какого цвета унутре неонки(связывание, конверсия, маппинг, итд) — мне не надо.

P>·>Но вот тут "params: [id], { [fn1],out: [fn2]}" ты именно это и тестируешь. Ты уж определись надо оно тебе или не надо... Или ты тестируешь то, что тебе не надо?!!
P>Это выхлоп билдера — в нем связывание, конверсия, маппинг и тд. Т.е. у нас кейс с метапрограммированием — результат фукнции это код другой функции. И я использую ровно тот же подход что и везде — сравнение результата. Только в данном случае наш результат это код.
Зачем нам тестировать выхлоп билдера? У билдера должны быть свои тесты. Ты сомневаешься, что какой-нибудь linq/jooq что-то не так нагенерит? И как это относится к тестированию ожиданий, а не кода "как написано"?

P>>>Чем сложнее логика построения запросов — тем больше тестов придется писать. Косвенные проверки не смогут побороть data complexity

P>·>Ну да. Больше логики — больше тестов. Причём тут "косвенность" проверок?
P>Я ж вам пример привел с фильтрами. Одними проверками по результату побороть data complexity не получится. А у вас ничего кроме этого и нет.
P>т.е. вы не сможете различить два кейса — правильный запрос, и запрос на некотором множеств результатов выдает данные похожие на нужные
P>Например — фильтр вырождается в true. А вот если мы накладываем ограничения на результирующий запрос, у нас эта часть исключается принципиально.
Не понимаю проблему. Если фильтр выродится в true, то субд вернёт те записи, которые мы не ожидаем, тест провалится.

P>>>Дональд Кнут на линии: "Остерегайтесь ошибок коде; я только доказал его правильность, но не проверял его."

P>·>Ты либо крестик, либо трусы. Док.программирование это спеки, контракты и верификаторы. Тесты тут непричём.
P>Простой вопрос был — как ваше доказательство проверит само себя если другой коллега начнет писать код как ему вздумается?
Верификатор выдаст ошибку. Почитай про какой-нибудь coq что-ли.

P>С тестами всё просто — в пулреквесте будут видны закомменченые тесты, или же билд не соберется и не попадёт на прод

Ну будет в коммите одном из десятка тестов вырожденный фильтр... И? Особенно весёлые штуки типа "A = B OR A <> B" из-за немножко не так поставленных скобочкек. Ты как равьювер будешь смотреть тесты и в уме интерпретировать sql запросы?

P>>>language agnostic описания, если вы про json, это отстой, т.к. удлинняет цикл разработки — есть шанс написать такой манифест, который хрен знает как заимплементить

P>·>Написать такой корявый манифест немного сложнее, чем сгенерить.
P>Наоборот. Руками затейники выдают такое, что ни один генератор проглотить не может. А если вы начинаете с аннотаций или хотя бы интерфейсов, то уж точно ясно, что это реализуемо.
Верно, но называется это code-first со всеми последствиями.

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

P>·>Тогда нафиг и не нужны аннотации, и уж тем более манифесты.
P>Наоборот. АПИ то никуда не делся, только работаем мы с ним немного не так, как вы привыкли. Тесты всё равно остаются. И их надо не ногой писать, а согласно определению апи. И документирование, и клиентский код, и много чего еще.
Апи будет просто набор интерфейсов и/или модели данных. С нулевой логикой.

P>>>design-first означает, что в первую очередь деливерится апи, а не абы что. А вот как вы его сформулируете — json, idl, grapql, grpc, интерфейсы на псевдокоде, java классы без реализации — вообще дело десятое, хоть plantuml или curl.

P>>>Здесь самое главное, что бы мы могли отдать описание всем, кто будет с ним взаимодействовать. А уже потом начнем работать над реализацией.
P>·>Так у тебя же в начале (first) пишется/меняется code
P>АПИ. Его формат непринципиален — интерфейс с аннотациями, json, idl, curl, абстрактные классы, просто набор функций. Главное, что бы реализации нет.
Главнее, чтобы логики не было.

>> и потом из него генерится описание, которые ты отдаёшь всем. Да ещё и разное описание, в зависимости от фазы луны.

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

>> Т.е. ты в первую очередь кодишь свои переменные, конфиги, логику тенантов, и лишь потом у тебя появляется описание.

P>Вы похоже не вдупляте — design-first = вначале деливерим АПИ, а потом уже реализация, куда входят переменные, конфиги, логика тенантов итд.
http-сервер это такой АПИ... Угу-угу.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Отредактировано 06.01.2024 21:49 · . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.