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

Б>>>А если код порефакторят и изменят вызовы?

P>>То же самое, что и у вас. Добавили 4й вызов — ваши моки, коих по количеству тестов, отвалятся.
·>Тебе уже раз тысячу написали, что НЕ НАДО ПИСАТЬ МОКИ по количеству. Моки (стабы?) пишутся по параметрам и условиям вызова.

В этом случае вы воссоздаете ваше собственное видение мокируемой подсистемы, которое нечем проверить на соответствие с ожиданиями, ну разве что и моки тоже покрыть тестами OMG!
В таких моках вы должны отразить все неявные зависимости
Лямбдой это просто — симулируем любую ситуацию
Мок на тест — сложнее, но тоже возможно
Мок на все — приплызд, если у нас сотня кейсов, то вам понадобится довольно сложный мок
И теперь именно у вас проблема — а ну, как у нас чтото поменялось в дизайне, логике итд
Далеко не факт что ваш особо умный мок начнет валить тесты

В моем случае эти лямбды всего лишь помогают тестировать через возвращаемое значение

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

·>Любопытно, а что если "repo.get" дёргается в цикле? Заведёшь массив лямд?!

Лямбда которая возвращает значения по списку
Re[26]: Что такое Dependency Rejection
От: · Великобритания  
Дата: 25.12.23 11:16
Оценка:
Здравствуйте, Pauel, Вы писали:

P>>>То же самое, что и у вас. Добавили 4й вызов — ваши моки, коих по количеству тестов, отвалятся.

P>·>Тебе уже раз тысячу написали, что НЕ НАДО ПИСАТЬ МОКИ по количеству. Моки (стабы?) пишутся по параметрам и условиям вызова.
P>В этом случае вы воссоздаете ваше собственное видение мокируемой подсистемы, которое нечем проверить на соответствие с ожиданиями, ну разве что и моки тоже покрыть тестами OMG!
А твоя лямбда что возвращает?

P>В таких моках вы должны отразить все неявные зависимости

P>Лямбдой это просто — симулируем любую ситуацию
Хинт: Лямбда это и есть частный случай мока (стаба). Разница лишь в том, что лямбда это ровно один метод, а мок (стаб) — может быть больше одного. Поэтому у тебя и получается, что приходится плодить лямбды там, где можно обойтись одним классом/интерфейсом.

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

P>·>Любопытно, а что если "repo.get" дёргается в цикле? Заведёшь массив лямд?!
P>Лямбда которая возвращает значения по списку
Какому ещё списку? "repo.get" может дёргаться в цикле, несколько раз, в зависимости от условий и даже результатов предыдущих вызовов и погоды на марсе. Да там вообще внутре какая-нибудь рекурсия может быть, да что угодно. Или ты опять о своих хоумпейджах рассуждаешь?
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Отредактировано 25.12.2023 11:18 · . Предыдущая версия .
Re[25]: Что такое Dependency Rejection
От: Pauel Беларусь http://blogs.rsdn.org/ikemefula
Дата: 25.12.23 13:49
Оценка:
Здравствуйте, ·, Вы писали:

P>>Вы и сами тащите "надо проверить old и new", и другого варианта не видите, как будто его нет. Можно же и дизайн поменять, что бы юнитами такое проверять. Но нет — вы топите за моки.

·>Есть, конечно, но другие варианты хуже. Больше писать кода и сложнее тестировать.

Наоборот — юнит тесты дешевле некуда.

P>>>>У вас — охотно верю.

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

Это у вас телепатия разыгралась? С чего вы взяли, что у меня лишнего кода бОльше? У меня основной подход к разработке это как раз выбрасывать код и покрывать оставшееся максимально дешовыми юнит тестами.
Но вам конечно же виднее.

P>>Со статьё давно всё ясно. crypto api и time это пример, которые показывают, за счет можно отказаться от моков, и что будет, если этого не делать.

·>Ни для crypto api, ни для time моки не нужны обычно не потому что "дизайн", а потому что они быстрые, детерминированные, с нулём внешних зависимостей.

Вы сейчас сами себя отрицаете:
Во первых, вы сами недавно поделились решением для hmac — "моками проверим то и это"
Во вторых, чуть далее, тоже недавно, вы рассказывали, как моками отрезали Time.now

P>>Это у вас на моках упадут все тесты всех операций апи. О том и речь. А на обычных юнит-тестах вы подкидываете новый набор значений, и всё путём.

·>Почему десяток? У тебя только десяток операций в api? Откуда взялся десяток?

Десяток значений. Эти значения вы можете в каких угодно тестах использовать.

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

P>>"достаточно ассертить его наличие" — вот вам и привязка к "как написано"
·>Не понял. Наличие hmac — это ожидание, бизнес-требование.

Это не повод моки тащить. Если значение hmac во всех кейсах совпадает с нашим, это куда лучшая проверка. Но вместо этого вы берете моки т.к. "это бизнес-требования"

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

·>Я тебе показывал как надо моками покрывать.

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

P>>Вы чего то недопоняли, но вместо уточнения начинаете валять дурака?

·>А толку уточнять. Я задал вопрос чего десяток — ответа так и не последовало.

Десяток значений, что очевидно. Что для time, что для токенов. Вот если вы тестируете свою либу date-time или hmac, там будет побольше значений, до сотни, может и больше.

P>>time api это пример дизайна, как можно уйти от комбинаторного взрыва.

·>Это не "дизайн". Это если ЯП умеет в duck typing и сооружать типы на ходу. Впрочем, тут явно фича применена не по месту.

Это никакой не duck-typing. Это статически типизированые фичи — spread и destructuring. Если какого то филда не хватает — компилятор так и скажет. Если какой то филд будет иметь не тот тип — компилятор говорит об этом.

P>>Принципиальное отличе of вместо from

·>Принципиальное отличие что тут три разных типа Date, Time и Offset — и их никак не перепутать.

Запросто — local time и server time будут все равно Time, опаньки. Есть LocalTime, AbsoluteTime, итд. Ошалеете покрывать все кейсы

·>Я предложил как решают задачу взаимодействия с неким абстрактным сложным API в тестах с моками (стабами?). То что это crypto — к сути дела не относится, я это сразу заявил в ответе на твой пример, перечитай. Так что это твои фантазии.


Вы показываете выбор в пользу моков, а не в пользу сравнения результата. Вы сами то это видите? Если у нас сотня кейсов в апи, ваш моковый подход превращается в тавтологию, тесты "как написано"
Эту самую сотню кейсов всё равно надо покрыть внятными тестами.

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


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

> В качестве аналогии — когда ты вставляешь CPU в материнку — тебе достаточно по сути smoke test, а не прогонять те миллионы тестов, которые были сделаны на фабрике для каждого кристалла и конденсатора.


Недостаточно — полно причин, когда CPU проходит смок-тест. А вот загрузка операционки, что уже много больше смок теста, уже кое что гарантирует. И то полно случаев, когда в магазине пусканул систему, всё хорошо. Принес домой — комп уже не включется, а бибикает.
Вобщем, понятно, откуда вы берете невалидные идеи.

·>У тебя же будет дополнительных xxxComposition на каждый метод в прод-коде и это надо будет всё как-то тестииовать в полном сборе.


Вы там похоже вовсю фантазируете.

P>>Кафка — это обычный эвент, как вариант, можно обойтись и простым юнитом, убедиться что функция возвращает объект-эвент.

P>>Если у вас такой роскоши нет — добавьте мок ровно на нотификацию, а не на кафку.
·>Так вызов метода — это и есть по сути эвент. По крайней мере, изоморфное понятие.

Эвент можно проверять как значение. А можно и моком. Значением дешевле.

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

·>Не знаю как у тебя, но у меня большинство ошибок именно логике в реалзиации методов, когда я тупо ошибаюсь в +1 вместо -1, from/to и т.п.

В том то и дело — и никакие моки этого не исправят. Потому и нужна проверка на результат.

P>>Здесь мы тестируем построение запроса

·>Ассерт-то в чём? Что текст запроса 'select * from users where id=?'? У тебя в тесте именно текст забит или что?

Я ж вам показал все что надо — deep equality + структура.

P>>·>Как проверить, что текст запроса хотя бы синтаксически корректен?

P>>Для этого нам всё равно придется выполнить его на реальной бд. Расскажите, как вы решите задачу вашими моками.
·>Я уже рассказывал. Интеграционным тестом repo+db. Текстов запроса в тестах нет, и быть не должно. Как-то так:

Вот видите — вам тоже нужны такие тесты Только вы предпочитаете косвенную проверку.
Кстати, как вы собираетесь пагинацию да фильтры тестировать? Подозреваю, сгенерируете несколько тысяч значений, и так для каждой таблицы?


P>>А здесь как вы моками обойдетесь?

·>Тут и не надо. Здесь же интеграция с субд тестируется. Моки (стабы?) будут дальше — в бл, в контроллере, етс.

Ну да, раз вы протащили в БЛ репозиторий, то и отрезать надо там же. А вариант сделать БЛ иначе, что бы хватило юнитов, вы отбрасываете заранее с формулировкой "по старинке надёжнее"

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

·>У тебя был такой код:
·>
·>const getUser = repository.getUser(id);
·>const oldUser = db.execute(getUser);
·>

·>Этот код выглядит так, что getUser выдаваемый из репы не зависит от db (и как ты заявил обещано в clean) и repo покрывается тестами независимо.

Есть такое. Они конечно же должны быть согласованы. Моя идея была в том, что бы тестировать построение запроса, а не мокать всё подряд.

·>В моём коде выше с MyRepo("connectionString") — мы тестируем только интеграцию двух частей repo + dbms. И ничего больше в таких тестах поднимать не надо.


И у меня примерно так же, только моков не будет.

P>>Что вас смущает? трансформация результата принимает ResultSet и возвращает вам нужный объект.

·>Так ведь надо не абы какой ResultSet, а именно тот, который реально возвращается из "select *". У тебя же эти по настоящему связанные сущности тестируются независимо. Что абсолютно бесполезно.

1 раз тестируются в составе интеграционного теста, такого же, как у вас. и много раз — в составе юнит-тестов, где нас интересуют подробности
— а что если в колонке пусто
— а что если значение слишком длинное
— а что если значение слишком короткое
— ...
И это в любом случае нужно делать, и никакие моки от этого не избавляют. Можно просто отказаться от таких тестов типа "у нас смок тест бд". Тоже вариант, и собственно моки здесь снова никак не помогают

P>>Расскажите, как вы это моками решите

·>Моки (стабы) — это для отпиливания зависимостей в тестах. Репу тестируем медленными и-тестами с более-менее реальной субд, а дальше моки (стабы) для быстрого тестирования всего остального.

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

P>>Это одно и то же, и в этом проблема. Я ж вам говорю, как можно иначе — но вы упорно видите только вариант из 90х-00х

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

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

P>>В новом подходе не надо БЛ прибивать ни к какой репе, ни к бд, ни к стораджу, ни к кафе, ни к внешним сервисам

·>Ага-ага.

Похоже, вам ктото запрещает посмотреть как это делается

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

·>Не понял, если возврат отключен, то и тест должен тестировать, что возврат отключен.

Очень даже понятно — вот некто неглядя вкомитал в конфиг что возвраты отключены. Ваши действия?

·>Примерное типичное кол-во сценариев реальной системе большего, чем хоумпейдж масштаба.


Из этого никак не следует, что и в тестах будет так же. Нам не нужно сравнивать трассы всех тестов вообще. Вы вы еще предложите трассы нагрузочных тестов сравнивать или трассы за весь спринт
Нас интересуют трассы конкретных тестов. Если вы можете такое выделить — акцептанс тесты для вас. Если у вас мешанина, и всё вызывает всё да помногу раз с аудитом вперемешку — акцептанс тесты вам не помогут.

P>>Неделя бодания и перекинули проблему наверх, только когда я сослался на федеральный закон

P>>Признавайтесь, ваша идея?
·>Только не пытайся доказать, что это у них "не работало", потому что тесты у них неправильные. Уж поверь, у них всё работало как надо (им).

После ссылки на федеральный закон вдруг "заработало" как надо.

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

·>Но ведь не "убирают", а переносят. Что добавляет и количество прод-кода и число тестов.

Тестов БЛ — больше, т.к. теперь можно покрывать дешовыми тестами самые разные кейсы, для того и делается.
Интеграционных — столько же, как и раньше, — по количеству АПИ и сценариев.

P>>Ага, сидим да годами ждём результаты одного теста. Вам самому не смешно?

·>Думаю ваш хоум-пейдж за час тестируется, в лучшем случае.

Полтора часа только сборка идет.

P>>А с аннотациями взял да и отрендерил манифест.

·>Гы-гы. У тебя же, ты сам говорил, что попало где попало — и в переменных окружения, и в конфигах и где угодно эти "аннотации" висят. Сваггер у вас каждые 5 минут новый, да?

Вы снова из 90х? Манифест генерируется по запросу, обычно http. Отсюда ясно, что и переменные, и конфиги, и аннотации будут учтены. Еще тенантность будет учитывать — для одного тенанта один апи доступен, для другого — другой.

·>Ты любишь сложность на пустом месте, притом в прод-коде, я уже понял. Но это плохо. Фреймворк это совсем другая весовая категория. Для того что ты пишешь достаточно extract method refactoring, а не новая архитектура с фреймворками.


Ровно наоборот. Чем проще, тем лучше. Фремворковый код он не то что бы особо сложный — просто разновидности вы будете каждый раз писать по месту. Или же отделяете, покрываете сотней тестов, и используете через аннотации без оглядки "а вдруг не сработает"
Re[26]: Что такое Dependency Rejection
От: · Великобритания  
Дата: 25.12.23 21:11
Оценка:
Здравствуйте, Pauel, Вы писали:

P>>>Вы и сами тащите "надо проверить old и new", и другого варианта не видите, как будто его нет. Можно же и дизайн поменять, что бы юнитами такое проверять. Но нет — вы топите за моки.

P>·>Есть, конечно, но другие варианты хуже. Больше писать кода и сложнее тестировать.
P>Наоборот — юнит тесты дешевле некуда.
И? С моками (стабами) тесты такие же дешёвые. Т.к. мы мочим всё медленное и дорогое. В этом и цель моков (стабов) — удешевлять тесты.

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

P>·>По факту пока что кода лишнего больше у тебя. Притом "вот фаулер написал моки — плохо, давайте накрутим слоёв в прод коде!".
P>Это у вас телепатия разыгралась? С чего вы взяли, что у меня лишнего кода бОльше?
В статье кода стало больше. Вот эти все getUser — это всё лишний код в проде. Твои "{...toFields}" — тоже лишний код, и это всё прод-код.

P>>>Со статьё давно всё ясно. crypto api и time это пример, которые показывают, за счет можно отказаться от моков, и что будет, если этого не делать.

P>·>Ни для crypto api, ни для time моки не нужны обычно не потому что "дизайн", а потому что они быстрые, детерминированные, с нулём внешних зависимостей.
P>Вы сейчас сами себя отрицаете:
P>Во первых, вы сами недавно поделились решением для hmac — "моками проверим то и это"
Это твоё было решение. Я его просто сделал вменяемым.

P>Во вторых, чуть далее, тоже недавно, вы рассказывали, как моками отрезали Time.now

Ага. И это одна строчка кода. Ты взамен предложил _фреймворк_ для этого же.

P>>>Это у вас на моках упадут все тесты всех операций апи. О том и речь. А на обычных юнит-тестах вы подкидываете новый набор значений, и всё путём.

P>·>Почему десяток? У тебя только десяток операций в api? Откуда взялся десяток?
P>Десяток значений. Эти значения вы можете в каких угодно тестах использовать.
Значений чего? И почему они лежат в одном файлике?

P>>>"достаточно ассертить его наличие" — вот вам и привязка к "как написано"

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

P>Если значение hmac во всех кейсах совпадает с нашим, это куда лучшая проверка. Но вместо этого вы берете моки т.к. "это бизнес-требования"

Это плохая проверка. Т.к. при изменении формата/алгоритма этого hmac тебе придётся менять тестовые данные в этих самых всех кейсах.

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

P>·>Я тебе показывал как надо моками покрывать.
P>Я про это и говорю — вы предлагаете писать мок на все случаи жизни.
Это твои фантазии.

P>Как убедиться что мокируемая система совпадает по поведению с вашим моком — не ясно.

Это не проблема моков. А проблема раздельного тестирования. У тебя ровно та же проблема: "Они конечно же должны быть согласованы".

P>>>Вы чего то недопоняли, но вместо уточнения начинаете валять дурака?

P>·>А толку уточнять. Я задал вопрос чего десяток — ответа так и не последовало.
P>Десяток значений, что очевидно. Что для time, что для токенов. Вот если вы тестируете свою либу date-time или hmac, там будет побольше значений, до сотни, может и больше.
Нет, совершенно не очевидно. И я всё равно не понял, какой time? каких токенов? какая своя либа?

P>>>time api это пример дизайна, как можно уйти от комбинаторного взрыва.

P>·>Это не "дизайн". Это если ЯП умеет в duck typing и сооружать типы на ходу. Впрочем, тут явно фича применена не по месту.
P>Это никакой не duck-typing. Это статически типизированые фичи — spread и destructuring. Если какого то филда не хватает — компилятор так и скажет. Если какой то филд будет иметь не тот тип — компилятор говорит об этом.
Насколько мне известно такое умеет только typescript из мейнстримовых яп.

P>>>Принципиальное отличе of вместо from

P>·>Принципиальное отличие что тут три разных типа Date, Time и Offset — и их никак не перепутать.
P>Запросто — local time и server time будут все равно Time, опаньки. Есть LocalTime, AbsoluteTime, итд. Ошалеете покрывать все кейсы
Я ничего не понял, но всё равно очень интересно. Если чё, то LocalTime/AbsoluteTime (это откуда кстати?) не просто так придумали, а потому что это действительно разные типы с разными свойствами, разной арифметикой и разными операциями. Это именно бизнес-область, календарь человеков — вещь такая, от которой если не достаточно ошалеть, будешь делать кучу ошибок.

P>·>Я предложил как решают задачу взаимодействия с неким абстрактным сложным API в тестах с моками (стабами?). То что это crypto — к сути дела не относится, я это сразу заявил в ответе на твой пример, перечитай. Так что это твои фантазии.

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

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

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

>> В качестве аналогии — когда ты вставляешь CPU в материнку — тебе достаточно по сути smoke test, а не прогонять те миллионы тестов, которые были сделаны на фабрике для каждого кристалла и конденсатора.

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

P>·>У тебя же будет дополнительных xxxComposition на каждый метод в прод-коде и это надо будет всё как-то тестииовать в полном сборе.

P>Вы там похоже вовсю фантазируете.
Вот это оно и есть:
{
load: () => repo1,
load2: () => svc,
... и так прокидываем все что надо
}

— эта лапша у тебя будет в каждом тесте, ещё такая же (почти!) копипастная лапша — и в прод-коде, которую придётся каждую покрывать отдельным и-тестом и это для каждого do_logic метода.

P>>>Кафка — это обычный эвент, как вариант, можно обойтись и простым юнитом, убедиться что функция возвращает объект-эвент.

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

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

P>·>Не знаю как у тебя, но у меня большинство ошибок именно логике в реалзиации методов, когда я тупо ошибаюсь в +1 вместо -1, from/to и т.п.
P>В том то и дело — и никакие моки этого не исправят. Потому и нужна проверка на результат.
Моки позволяют писать очень много быстрых и легковесных тестов и легко отлаживать.

P>>>Здесь мы тестируем построение запроса

P>·>Ассерт-то в чём? Что текст запроса 'select * from users where id=?'? У тебя в тесте именно текст забит или что?
P>Я ж вам показал все что надо — deep equality + структура.
deep equality чего с чем? Что написано в коде теста?

P>>>·>Как проверить, что текст запроса хотя бы синтаксически корректен?

P>>>Для этого нам всё равно придется выполнить его на реальной бд. Расскажите, как вы решите задачу вашими моками.
P>·>Я уже рассказывал. Интеграционным тестом repo+db. Текстов запроса в тестах нет, и быть не должно. Как-то так:
P>Вот видите — вам тоже нужны такие тесты Только вы предпочитаете косвенную проверку.
В чём косвенность?

P>Кстати, как вы собираетесь пагинацию да фильтры тестировать? Подозреваю, сгенерируете несколько тысяч значений, и так для каждой таблицы?

Зачем несколько тысяч? Тысячи нужны для перф-тестов. Для тестирования логики достаточно нескольких значений. Но, впрочем, если тебе очень хочется — сгенерировать тысячу значений — есть такая штука как for-loop, например.

P>>>А здесь как вы моками обойдетесь?

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

P>·>У тебя был такой код:

P>·>
P>·>const getUser = repository.getUser(id);
P>·>const oldUser = db.execute(getUser);
P>·>

P>·>Этот код выглядит так, что getUser выдаваемый из репы не зависит от db (и как ты заявил обещано в clean) и repo покрывается тестами независимо.
P>Есть такое. Они конечно же должны быть согласованы. Моя идея была в том, что бы тестировать построение запроса, а не мокать всё подряд.
Зачем тебе тестировать построение запроса? С т.з. "ожидаемого результата", за который ты так ратуешь, совершенно пофиг какой запрос построился. Главное, что если его выполнить и распарсить recordset — вернётся ожидаемый результат.

P>·>В моём коде выше с MyRepo("connectionString") — мы тестируем только интеграцию двух частей repo + dbms. И ничего больше в таких тестах поднимать не надо.

P>И у меня примерно так же, только моков не будет.
В этом моём коде выше не было моков.

P>>>Что вас смущает? трансформация результата принимает ResultSet и возвращает вам нужный объект.

P>·>Так ведь надо не абы какой ResultSet, а именно тот, который реально возвращается из "select *". У тебя же эти по настоящему связанные сущности тестируются независимо. Что абсолютно бесполезно.
P>1 раз тестируются в составе интеграционного теста, такого же, как у вас. и много раз — в составе юнит-тестов, где нас интересуют подробности
P>- а что если в колонке пусто
Именно. А по итогу получится, что при вставке бд пустую колонку заменяет на дефолтное значение и при выборке там пусто быть просто не может; и окажется, что это дефолтное значение не очень правильное. Получается, что твои тесты тестируют какую-то хрень, "как написано".

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

Ещё раз повторю. Это и-тесты репо+бд. А моки избавляют от необходимости иметь бд при тестировании всего остального.

P>>>Расскажите, как вы это моками решите

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

P>Зато мне нужно будет несколько медленных тестов самого useCase.

Не несколько, а дофига.

P>>>Это одно и то же, и в этом проблема. Я ж вам говорю, как можно иначе — но вы упорно видите только вариант из 90х-00х

P>·>Ты показал это "иначе", но там всё ещё хуже прибито. Вплоть до побуквенного сравнения текста sql.
P>-
P>Вы так и не рассказали, чем это хуже. Вы предложили конский запрос тестировать косвенно
Тем, что тесты пишутся в первую очередь для людей. Может я просто недостаточно профессионал, но я не умею в уме исполнять sql-запросы и уж точно запросто пропущу мелкие синтаксические ошибки. Вот user.getName() == "Vasya" — мой мозг понять способен, а вот "select * from users where id=?" — я не понимаю эти все закорючки, тем более с учётом того, что каждая субд любит это немного по-разному. Может надо было "from user where" написать? Или "from [user] where"? А фиг знает... Я понятия не имею какой sql правильный.

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

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

P>·>Примерное типичное кол-во сценариев реальной системе большего, чем хоумпейдж масштаба.

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

P>·>Только не пытайся доказать, что это у них "не работало", потому что тесты у них неправильные. Уж поверь, у них всё работало как надо (им).

P>После ссылки на федеральный закон вдруг "заработало" как надо.
Именно. И причём тут тесты?

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

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

P>>>Ага, сидим да годами ждём результаты одного теста. Вам самому не смешно?

P>·>Думаю ваш хоум-пейдж за час тестируется, в лучшем случае.
P>Полтора часа только сборка идет.
Ээээ. шутишь? А тесты сколько времени занимают?

P>>>А с аннотациями взял да и отрендерил манифест.

P>·>Гы-гы. У тебя же, ты сам говорил, что попало где попало — и в переменных окружения, и в конфигах и где угодно эти "аннотации" висят. Сваггер у вас каждые 5 минут новый, да?
P>Вы снова из 90х? Манифест генерируется по запросу, обычно http. Отсюда ясно, что и переменные, и конфиги, и аннотации будут учтены.
Сваггер — это спека для клиентов. Она статическая и меняться должна в идеале никогда.

P>Еще тенантность будет учитывать — для одного тенанта один апи доступен, для другого — другой.

Жуть.

P>·>Ты любишь сложность на пустом месте, притом в прод-коде, я уже понял. Но это плохо. Фреймворк это совсем другая весовая категория. Для того что ты пишешь достаточно extract method refactoring, а не новая архитектура с фреймворками.

P>Ровно наоборот. Чем проще, тем лучше. Фремворковый код он не то что бы особо сложный — просто разновидности вы будете каждый раз писать по месту. Или же отделяете, покрываете сотней тестов, и используете через аннотации без оглядки "а вдруг не сработает"
Есть такая штука, называется code reuse.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Отредактировано 25.12.2023 21:19 · . Предыдущая версия . Еще …
Отредактировано 25.12.2023 21:14 · . Предыдущая версия .
Re[17]: Что такое Dependency Rejection
От: Буравчик Россия  
Дата: 26.12.23 14:29
Оценка:
Здравствуйте, ·, Вы писали:

·>Не очень понимаю проблему. Что можно нереального сделать в моке bl.modifyUser(id, newUserData) не так? Суть в том, что контроллер зовёт modifyUser и конвертирует результат:

·>
·>class UserController {
·>  Response update(Request r) {
·>    var id = extractId(r);
·>    var data = extractData(r);
·>    var result = bl.modifyUser(id, data);
·>    return result.isSuccess ? Response(r.correlationId, prepare(result)) : Failure("sorry");
·>  }
·>}
·>

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

Хочу вот здесь уточнить про тестирование контроллера

Правильно понимаю, что отдельно тестируете контроллер (мокаете bl) и отдельно тестируете bl (мокаете repo)?
Я придерживаюсь такого подхода, но некоторые тестируют сразу контоллер+логику вместе (мокая repo и не мокая БЛ).

Хочу понять плюсы и минусы подходов. На мой взгляд так:
Плюсы если тестировать вместе: тестируется видимое поведение системы, типа "входной json -> выходной json", а также не ломаются тесты во время рефакторинга связки контроллер-action (хотя здесь редко что-то изменяется).
Плюсы если тестировать отдельно контроллер: тесты bl описываются в рамках сущностей БЛ, получаются более "бизнесовыми".

Или может как-то по-другому тестируете контроллер, например, выделяя логику валидации и сериализации в чистые функции.
В общем, есть какие-то принципы?
Best regards, Буравчик
Re[18]: Что такое Dependency Rejection
От: · Великобритания  
Дата: 26.12.23 16:08
Оценка:
Здравствуйте, Буравчик, Вы писали:

Б>Хочу вот здесь уточнить про тестирование контроллера

Б>Правильно понимаю, что отдельно тестируете контроллер (мокаете bl) и отдельно тестируете bl (мокаете repo)?
Тут зависит от приложения. Контроллер и бл — это слои нужные в случае если у тебя есть несколько разных представлений. Назначение контроллера — разбирать входные запросы, что-то делать внутре, и формировать ответы. В мире микросервисов выделение отдельного слоя далеко не всегда нужно. Т.к. контроллер один и в него можно сразу запрятать всю бл, оттуда сразу ходить в репо.

Отдельный слой бл нужен если у тебя есть несколько представлений. Например, контроллер http с request-response и тут же контроллер каких-нибудь protobuf с асинхронными сообщениями. А бизнес-логика — шарится общая.

Б>Я придерживаюсь такого подхода, но некоторые тестируют сразу контоллер+логику вместе (мокая repo и не мокая БЛ).

Можно. Собираешь wiring-модуль, в котором компоненты бл и контроллеры соединяются в единое и в тестах туда инжектится мок репы, а в проде — реальный репо с субд.

Б>В общем, есть какие-то принципы?

Я думаю надо выбирать не по каким-то принципам, а по тому где сколько кода и насколько его легко тестировать. Если и в контроллере, и в бл много логики, то по частям тестировать может быть проще. А если контроллер простой, то писать тривиальные тесты скучно и бесполезно.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[19]: Что такое Dependency Rejection
От: Pauel Беларусь http://blogs.rsdn.org/ikemefula
Дата: 27.12.23 10:09
Оценка:
Здравствуйте, ·, Вы писали:

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


Это и есть основная причина для моков — называется монолитный дизайн.
Re[27]: Что такое Dependency Rejection
От: Pauel Беларусь http://blogs.rsdn.org/ikemefula
Дата: 27.12.23 11:09
Оценка: +1
Здравствуйте, ·, Вы писали:

P>>Наоборот — юнит тесты дешевле некуда.

·>И? С моками (стабами) тесты такие же дешёвые. Т.к. мы мочим всё медленное и дорогое. В этом и цель моков (стабов) — удешевлять тесты.

Для начала это вы сами сделали тесты дорогими, когда поместили БЛ прямо в контроллер

P>>Это у вас телепатия разыгралась? С чего вы взяли, что у меня лишнего кода бОльше?

·>В статье кода стало больше. Вот эти все getUser — это всё лишний код в проде. Твои "{...toFields}" — тоже лишний код, и это всё прод-код.

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

P>>Во первых, вы сами недавно поделились решением для hmac — "моками проверим то и это"

·>Это твоё было решение. Я его просто сделал вменяемым.

Мое решение — просто сравнивать по значению. А вы все моки тащите.

P>>Во вторых, чуть далее, тоже недавно, вы рассказывали, как моками отрезали Time.now

·>Ага. И это одна строчка кода. Ты взамен предложил _фреймворк_ для этого же.

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

P>>Десяток значений. Эти значения вы можете в каких угодно тестах использовать.

·>Значений чего? И почему они лежат в одном файлике?

hmac, time, итд. В одном файле — что бы чинить не 100 тестов, а всего 10 значений когда вы поменяете hmac 256 на 512.
Т.е. вместо того, что бы хардкодить константы по всем тестами, складываем их в один файл, что бы сразу видеть всю картину
И из этого файла таскаете паттерны и тд

P>>Это не повод моки тащить.

·>Конечно, это я сразу тебе сказал. Моки для hmac — ты притащил. Перечитай топик, все ходы записаны.

У вас требование hmac стало основанием моки протаскивать. Вот это непонятно. Значением проверить никак нельзя, да?

P>>Если значение hmac во всех кейсах совпадает с нашим, это куда лучшая проверка. Но вместо этого вы берете моки т.к. "это бизнес-требования"

·>Это плохая проверка. Т.к. при изменении формата/алгоритма этого hmac тебе придётся менять тестовые данные в этих самых всех кейсах.

1 При изменении алгоритма, формата итд, сломаются ваши моки
2 Тестовые данные не размазываются по кейсам, а хранятся в файле тестовые-данные.json


P>>Как убедиться что мокируемая система совпадает по поведению с вашим моком — не ясно.

·>Это не проблема моков. А проблема раздельного тестирования. У тебя ровно та же проблема: "Они конечно же должны быть согласованы".

Эта согласованность проверится любым интеграционным тестом который вызовет контроллер.

P>>Десяток значений, что очевидно. Что для time, что для токенов. Вот если вы тестируете свою либу date-time или hmac, там будет побольше значений, до сотни, может и больше.

·>Нет, совершенно не очевидно. И я всё равно не понял, какой time? каких токенов? какая своя либа?

Ну вот нам нужен тест nextFriday() — функция, которая возвращает дату следующей пятницы
1 вариант, как вы любите, с моками — мокаем Time.now, делаем свои вычисления и проверяем, что наши вычисления совпадают с теми, что возвращает функция
Поздравляю, это классика тавтологических тестов

2 вариант, кладем в файлик тестовы-данные.json "время", "правильный результат", штуки 10 строчк. Меняем дизайн на nextFriday(now) и теперь у нас простой юнит-тест
@params(samples.nextFiday); // вот здесь все наши константы
"next friday"(now, result) {
   expect(nextFriday(now)).to.deep.eq(result)
}


P>>Эту самую сотню кейсов всё равно надо покрыть внятными тестами.

·>Я пытаюсь донести мысль, что пофиг — моки или результаты, это технические детали. Это ортогонально тестам "как написано", т.к. тесты это про семантику.

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

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

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

1. сводить все к простым вычислениям
2. если нет другого варианта, как в примере Буравчика, изолировать от зависимостей, что бы логика всё равно зависела от своих параметров.

P>>Вобщем, понятно, откуда вы берете невалидные идеи.

·>Даже если так. Загрузка операционки это один тест. Кристалл и каждый компонент современного компьютера на фабриках тестируют тысячами-миллионами тестов.

Загрузка операционки это выполнение несколько миллионов операций на процессоре и связанных с ним устройствах.

·>- эта лапша у тебя будет в каждом тесте, ещё такая же (почти!) копипастная лапша — и в прод-коде, которую придётся каждую покрывать отдельным и-тестом и это для каждого do_logic метода.


Этот самый и-тест по любому нужен, для каждого do_login метода. Как бы вы не выкручивались. У меня метод do_logic будет линейным, заниматься только связыванием.

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

P>>Эвент можно проверять как значение. А можно и моком. Значением дешевле.

·>Что значит "дешевле"? И то, и то выполняется микросекунды и никакой сети-диска не требует.

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

P>>В том то и дело — и никакие моки этого не исправят. Потому и нужна проверка на результат.

·>Моки позволяют писать очень много быстрых и легковесных тестов и легко отлаживать.

Простые юнит тесты еше проще и быстрее. Как вы с моками будете дизайн менять?

P>>Я ж вам показал все что надо — deep equality + структура.

·>deep equality чего с чем? Что написано в коде теста?

Я ж вам все показал. Код теста почти полностью состоит из
1. вызов нужной функции которая строит запрос
2. паттерн запроса
3. expect(buildRequest(params)).to.deep.eq(pattern)

P>>·>Я уже рассказывал. Интеграционным тестом repo+db. Текстов запроса в тестах нет, и быть не должно. Как-то так:

P>>Вот видите — вам тоже нужны такие тесты Только вы предпочитаете косвенную проверку.
·>В чём косвенность?

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

P>>Кстати, как вы собираетесь пагинацию да фильтры тестировать? Подозреваю, сгенерируете несколько тысяч значений, и так для каждой таблицы?

·>Зачем несколько тысяч? Тысячи нужны для перф-тестов. Для тестирования логики достаточно нескольких значений. Но, впрочем, если тебе очень хочется — сгенерировать тысячу значений — есть такая штука как for-loop, например.

Много фильтров и их комбинаций, да еще и пагинация

P>>Ну да, раз вы протащили в БЛ репозиторий, то и отрезать надо там же. А вариант сделать БЛ иначе, что бы хватило юнитов, вы отбрасываете заранее с формулировкой "по старинке надёжнее"

·>Так ведь юниты и "делать не по старинке" — это не самоцель. Целью может быть — писать поменьше прод кода, ловить баги как можно раньше и быстрее, упрощать разработку, отладку, етс.

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

P>>Есть такое. Они конечно же должны быть согласованы. Моя идея была в том, что бы тестировать построение запроса, а не мокать всё подряд.

·>Зачем тебе тестировать построение запроса? С т.з. "ожидаемого результата", за который ты так ратуешь, совершенно пофиг какой запрос построился. Главное, что если его выполнить и распарсить recordset — вернётся ожидаемый результат.

Затем, что самое сложное в БД это построение или запроса к ORM, или SQL. Слишком много запросов похожих на тот, что нам нужен, но нерабочих.
Вот на той неделе был случай — некто заимплементил фильтры, которые на некоторых наборах параметров вырождаются в пустую строку, и запрос каждый раз булшитит десятки миллионов записей в таблице
Зато на тестовой бд всё хорошо

P>>- а что если в колонке пусто

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

Смотрите выше, пример про десятки миллионов записей как раз для вас.

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

·>Ещё раз повторю. Это и-тесты репо+бд. А моки избавляют от необходимости иметь бд при тестировании всего остального.

А можно поменять дизайн. Но для вас это не вариант, вы по старинке!

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

·>Толку правда никакого от таких юнит-тестов, т.к. они тестируют "как написано".

Они тестируют построение запроса, например, построение тех самых фильтров.

·>Тем, что тесты пишутся в первую очередь для людей. Может я просто недостаточно профессионал, но я не умею в уме исполнять sql-запросы и уж точно запросто пропущу мелкие синтаксические ошибки. Вот user.getName() == "Vasya" — мой мозг понять способен, а вот "select * from users where id=?" — я не понимаю эти все закорючки, тем более с учётом того, что каждая субд любит это немного по-разному. Может надо было "from user where" написать? Или "from [user] where"? А фиг знает... Я понятия не имею какой sql правильный.


Если понятия не имеете, то скорее всего и запросы ваши будут работать приблизительно. Например, фильтры будут работать только на тестовых данных

P>>Очень даже понятно — вот некто неглядя вкомитал в конфиг что возвраты отключены. Ваши действия?

·>Ни разу такой проблемы не припомню. Это как "неглядя"? Конфиг, если что, бизнес/operations правят. Если это не должно отключаться, так я просто конфиг грохну.

Знакомая ситуация — какую проблему ни назови, у вас она принципиально невозможна.

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

·>Что за трассы тестов? Мы об аудите говорим. Аудит пишется практически каждым действием, т.е. почти каждым тестом бл. Значит при изменении формата аудита — изменится результат всех таких тестов.

Если вы протащили аудит прямо в бл, то так и будет. А если бл не имеет зависимостей на репу и бд, то для аудита у нас только одно место — там где применяем изменения. И таких тестов у нас много быть не может.


P>>Интеграционных — столько же, как и раньше, — по количеству АПИ и сценариев.

·>АПИ и сценарии — это и есть БЛ.

У вас уже и API стал бизнес логикой

P>>Полтора часа только сборка идет.

·>Ээээ. шутишь? А тесты сколько времени занимают?

Процентов 30-40 от сборки.

P>>Вы снова из 90х? Манифест генерируется по запросу, обычно http. Отсюда ясно, что и переменные, и конфиги, и аннотации будут учтены.

·>Сваггер — это спека для клиентов. Она статическая и меняться должна в идеале никогда.

Вы что, в первой версии приложения знаете, какие фичи понадобятся в течении следующих 10 лет? АПИ всегда эволюционирует
Если манифест держать кодом, то это хорошая заявочка на рассинхронизацию — у нас работает, а у клиентов — нет.
Re[20]: Что такое Dependency Rejection
От: Буравчик Россия  
Дата: 27.12.23 13:37
Оценка: +1
Здравствуйте, Pauel, Вы писали:

P>Это и есть основная причина для моков — называется монолитный дизайн.


Проблемы обычно кроются в зависимостях, которые трудно разорвать. Эту проблему решает DI. Моки ортогональны.

Что подразумеваешь под "монолитный дизайн"?
Best regards, Буравчик
Re[20]: Что такое Dependency Rejection
От: · Великобритания  
Дата: 27.12.23 17:11
Оценка:
Здравствуйте, Pauel, Вы писали:

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

P>Это и есть основная причина для моков — называется монолитный дизайн.
С опытом понимаешь, что важно не то как оно называется или что на сегодня модно у фаулеров, а то что лучше всего подходит для конкретной разрабатываемой системы. Для простенького микросервиса проще запилить всё в трёх классах, а не накручивать десять слоёв на пять фреймворков.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[21]: Что такое Dependency Rejection
От: Pauel Беларусь http://blogs.rsdn.org/ikemefula
Дата: 27.12.23 19:13
Оценка:
Здравствуйте, Буравчик, Вы писали:

P>>Это и есть основная причина для моков — называется монолитный дизайн.


Б>Проблемы обычно кроются в зависимостях, которые трудно разорвать. Эту проблему решает DI. Моки ортогональны.


Моки это привязка к конкретным вызовам, параметрам, последовательностям.
То есть — тавтологические тесты.

Монолитный — посмотрите ваш первый вариант, логика и зависимости вперемешку. Потому полностью уйти от моков не выйдет. У меня почти во всех случаях получается избавиться от такого дизайна и свести к простым юнит тестам.
Но если вы изначально считаете моки чем то хороши, естественно, более простой вариант в голову и не придет
Re[22]: Что такое Dependency Rejection
От: Буравчик Россия  
Дата: 27.12.23 20:22
Оценка: +1
Здравствуйте, Pauel, Вы писали:

P>Моки это привязка к конкретным вызовам, параметрам, последовательностям.


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

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


Если говорить про внешние зависимости, твои лямбды это и есть моки. Никуда от них не ушел.

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


Я стараюсь выбирать подходящий инструмент (более простой).

Если я вижу сложную логику, то выделяю ее в отдельную функцию и тестирую как чистую функцию (входные параметры -> результат)
Если я вижу много взаимодействия с внешними зависимостями, то использую моки. Это позволяет протестировать поведение как обычную функцию (входные параметры + "мок-данные из внешних систем" -> результат)
Best regards, Буравчик
Re[26]: Что такое Dependency Rejection
От: Буравчик Россия  
Дата: 27.12.23 20:24
Оценка:
Здравствуйте, Pauel, Вы писали:

P>·>Любопытно, а что если "repo.get" дёргается в цикле? Заведёшь массив лямд?!


P>Лямбда которая возвращает значения по списку


Здесь я тоже не понял. Можно пример?
Best regards, Буравчик
Re[28]: Что такое Dependency Rejection
От: · Великобритания  
Дата: 28.12.23 00:28
Оценка:
Здравствуйте, Pauel, Вы писали:

P>>>Наоборот — юнит тесты дешевле некуда.

P>·>И? С моками (стабами) тесты такие же дешёвые. Т.к. мы мочим всё медленное и дорогое. В этом и цель моков (стабов) — удешевлять тесты.
P>Для начала это вы сами сделали тесты дорогими, когда поместили БЛ прямо в контроллер
Ты опять скрываешь что ты понимаешь под дороговизной.

P>>>Это у вас телепатия разыгралась? С чего вы взяли, что у меня лишнего кода бОльше?

P>·>В статье кода стало больше. Вот эти все getUser — это всё лишний код в проде. Твои "{...toFields}" — тоже лишний код, и это всё прод-код.
P>Вы все еще про статью, тут я ничем помочь не могу.
getUser и toFields это не про статью.

P>·>Это твоё было решение. Я его просто сделал вменяемым.

P>Мое решение — просто сравнивать по значению. А вы все моки тащите.
У тебя было expect...toBeCalledWith — это было совершенно неправильное решение. Я просто попытался объяснить, что так делать не надо и показал как надо, сделав оговорку "Если не задаваться вопросом, зачем вообще вдруг понадобилось мокать hmac".

P>>>Во вторых, чуть далее, тоже недавно, вы рассказывали, как моками отрезали Time.now

P>·>Ага. И это одна строчка кода. Ты взамен предложил _фреймворк_ для этого же.
P>Не одна — для каждого из тестов вам нужен будет этот мок
Зачем для каждого? Этот мок можно куда-нибудь в setUp вынести.

P>Проще передавать время как значение и тестировать дешовыми юнит тестами

Конечно, проще. Но не везде же.

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

В топку фреймворки. Мне не нужен фреймворк ради экономии одной строчки в _тестовом_ коде.

P>>>Десяток значений. Эти значения вы можете в каких угодно тестах использовать.

P>·>Значений чего? И почему они лежат в одном файлике?
P>hmac, time, итд. В одном файле — что бы чинить не 100 тестов, а всего 10 значений когда вы поменяете hmac 256 на 512.
Я может что-то не знаю, но обычно hmac зависит от каждого теста — контрольная сумма сериализованного тела ответа. У тебя в твоём хоумпейдже на всю функциональность хватает 10 тестов?

P>>>Это не повод моки тащить.

P>·>Конечно, это я сразу тебе сказал. Моки для hmac — ты притащил. Перечитай топик, все ходы записаны.
P>У вас требование hmac стало основанием моки протаскивать. Вот это непонятно. Значением проверить никак нельзя, да?
Ты врёшь. Цитирую себя: "Если не задаваться вопросом, зачем вообще вдруг понадобилось мокать hmac". Моки в hmac притащил ты сам. Так что с собой спорь.

P>>>Если значение hmac во всех кейсах совпадает с нашим, это куда лучшая проверка. Но вместо этого вы берете моки т.к. "это бизнес-требования"

P>·>Это плохая проверка. Т.к. при изменении формата/алгоритма этого hmac тебе придётся менять тестовые данные в этих самых всех кейсах.
P>1 При изменении алгоритма, формата итд, сломаются ваши моки
Не сломаются.

P>2 Тестовые данные не размазываются по кейсам, а хранятся в файле тестовые-данные.json

Не размазываются.

P>>>Как убедиться что мокируемая система совпадает по поведению с вашим моком — не ясно.

P>·>Это не проблема моков. А проблема раздельного тестирования. У тебя ровно та же проблема: "Они конечно же должны быть согласованы".
P>Эта согласованность проверится любым интеграционным тестом который вызовет контроллер.
Не любым.

P>>>Десяток значений, что очевидно. Что для time, что для токенов. Вот если вы тестируете свою либу date-time или hmac, там будет побольше значений, до сотни, может и больше.

P>·>Нет, совершенно не очевидно. И я всё равно не понял, какой time? каких токенов? какая своя либа?
P>Ну вот нам нужен тест nextFriday() — функция, которая возвращает дату следующей пятницы
P>1 вариант, как вы любите, с моками — мокаем Time.now, делаем свои вычисления и проверяем, что наши вычисления совпадают с теми, что возвращает функция
P>Поздравляю, это классика тавтологических тестов
P>2 вариант, кладем в файлик тестовы-данные.json "время", "правильный результат", штуки 10 строчк. Меняем дизайн на nextFriday(now) и теперь у нас простой юнит-тест
Ты подменил задание, а не дизайн. Вначале была функция nextFriday(), потом вдруг стала nextFriday(now). Если твоя система должна по контракту клиентам выдавать операцию nextFriday(), то поменять сигнатуру ты не можешь.

P>>>Эту самую сотню кейсов всё равно надо покрыть внятными тестами.

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

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

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

P>2. если нет другого варианта, как в примере Буравчика, изолировать от зависимостей, что бы логика всё равно зависела от своих параметров.

У тебя зависимости не изолировались, просто перелилось из пустого в порожнее. Кода стало больше.

P>>>Вобщем, понятно, откуда вы берете невалидные идеи.

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

P>·>- эта лапша у тебя будет в каждом тесте, ещё такая же (почти!) копипастная лапша — и в прод-коде, которую придётся каждую покрывать отдельным и-тестом и это для каждого do_logic метода.

P>Этот самый и-тест по любому нужен, для каждого do_login метода. Как бы вы не выкручивались. У меня метод do_logic будет линейным, заниматься только связыванием.
Не по любому. Я тебе уже рассказывал как.

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

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

P>>>Эвент можно проверять как значение. А можно и моком. Значением дешевле.

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

P>>>В том то и дело — и никакие моки этого не исправят. Потому и нужна проверка на результат.

P>·>Моки позволяют писать очень много быстрых и легковесных тестов и легко отлаживать.
P>Простые юнит тесты еше проще и быстрее. Как вы с моками будете дизайн менять?
Ещё раз повторюсь — разница чисто техническая.

P>>>Я ж вам показал все что надо — deep equality + структура.

P>·>deep equality чего с чем? Что написано в коде теста?
P>Я ж вам все показал. Код теста почти полностью состоит из
P>1. вызов нужной функции которая строит запрос
P>2. паттерн запроса
P>3. expect(buildRequest(params)).to.deep.eq(pattern)
Что за паттерн-то? Полный код можешь написать? Вот ты рассказал, что оно выглядит так:
{
  sql: 'select * from users where id=?',
  params: [id],
  transformations: {
     in: [fn1],
     out: [fn2]
  }
}

Какой код будет в тесте для проверки этого?

P>>>·>Я уже рассказывал. Интеграционным тестом repo+db. Текстов запроса в тестах нет, и быть не должно. Как-то так:

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

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

Что значит "не тот"? Как проверить тот он или не тот? Как в принципе узнать как должен выглядеть тот?

P>>>Кстати, как вы собираетесь пагинацию да фильтры тестировать? Подозреваю, сгенерируете несколько тысяч значений, и так для каждой таблицы?

P>·>Зачем несколько тысяч? Тысячи нужны для перф-тестов. Для тестирования логики достаточно нескольких значений. Но, впрочем, если тебе очень хочется — сгенерировать тысячу значений — есть такая штука как for-loop, например.
P>Много фильтров и их комбинаций, да еще и пагинация
И? зачем для этого несколько тысяч значений? У тебя там комбинаторно рвануло, да?

P>>>Ну да, раз вы протащили в БЛ репозиторий, то и отрезать надо там же. А вариант сделать БЛ иначе, что бы хватило юнитов, вы отбрасываете заранее с формулировкой "по старинке надёжнее"

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

P>>>Есть такое. Они конечно же должны быть согласованы. Моя идея была в том, что бы тестировать построение запроса, а не мокать всё подряд.

P>·>Зачем тебе тестировать построение запроса? С т.з. "ожидаемого результата", за который ты так ратуешь, совершенно пофиг какой запрос построился. Главное, что если его выполнить и распарсить recordset — вернётся ожидаемый результат.
P>Затем, что самое сложное в БД это построение или запроса к ORM, или SQL. Слишком много запросов похожих на тот, что нам нужен, но нерабочих.
Построение запроса это детали реализации, важно что построенный запрос вернёт.

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

P>Зато на тестовой бд всё хорошо
Если эти наборы параметров забыли протестировать, то чем твой getUser поможет?

P>>>- а что если в колонке пусто

P>·>Именно. А по итогу получится, что при вставке бд пустую колонку заменяет на дефолтное значение и при выборке там пусто быть просто не может; и окажется, что это дефолтное значение не очень правильное. Получается, что твои тесты тестируют какую-то хрень, "как написано".
P>Смотрите выше, пример про десятки миллионов записей как раз для вас.
Было у вас, а тебе хочется верить, что про нас... опять фантазии.

P>·>Тем, что тесты пишутся в первую очередь для людей. Может я просто недостаточно профессионал, но я не умею в уме исполнять sql-запросы и уж точно запросто пропущу мелкие синтаксические ошибки. Вот user.getName() == "Vasya" — мой мозг понять способен, а вот "select * from users where id=?" — я не понимаю эти все закорючки, тем более с учётом того, что каждая субд любит это немного по-разному. Может надо было "from user where" написать? Или "from [user] where"? А фиг знает... Я понятия не имею какой sql правильный.

P>Если понятия не имеете, то скорее всего и запросы ваши будут работать приблизительно. Например, фильтры будут работать только на тестовых данных
У тебя тоже.

P>>>Очень даже понятно — вот некто неглядя вкомитал в конфиг что возвраты отключены. Ваши действия?

P>·>Ни разу такой проблемы не припомню. Это как "неглядя"? Конфиг, если что, бизнес/operations правят. Если это не должно отключаться, так я просто конфиг грохну.
P>Знакомая ситуация — какую проблему ни назови, у вас она принципиально невозможна.
Ты не поверишь, но некоторые классы проблем легко и гарантированно решаются. Проще не создавать себе трудности, чем их героически решать немоками.

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

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

P>>>Интеграционных — столько же, как и раньше, — по количеству АПИ и сценариев.

P>·>АПИ и сценарии — это и есть БЛ.
P>У вас уже и API стал бизнес логикой
Если я правильно понял, то тут под API подразумевался интерфейс системы для внешних клиентов. Т.е. то, о чём бизнес договаривается с клиентами.
А у вас уже и сценарии перестали быть бизнес логикой

P>>>Полтора часа только сборка идет.

P>·>Ээээ. шутишь? А тесты сколько времени занимают?
P>Процентов 30-40 от сборки.
А что потом после сборки?

P>>>Вы снова из 90х? Манифест генерируется по запросу, обычно http. Отсюда ясно, что и переменные, и конфиги, и аннотации будут учтены.

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

P>Если манифест держать кодом, то это хорошая заявочка на рассинхронизацию — у нас работает, а у клиентов — нет.

Я вообще за contract-first.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[27]: Что такое Dependency Rejection
От: · Великобритания  
Дата: 28.12.23 00:34
Оценка:
Здравствуйте, Буравчик, Вы писали:

P>>·>Любопытно, а что если "repo.get" дёргается в цикле? Заведёшь массив лямд?!

P>>Лямбда которая возвращает значения по списку
Б>Здесь я тоже не понял. Можно пример?
Было class Repo{ User get(int id)} он гениально предлагает class Repo{ List<User> get(List<int> id)}, иначе ведь в лямбду не заворачивается...
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[29]: Что такое Dependency Rejection
От: Pauel Беларусь http://blogs.rsdn.org/ikemefula
Дата: 28.12.23 11:04
Оценка: 80 (1)
Здравствуйте, ·, Вы писали:

P>>·>И? С моками (стабами) тесты такие же дешёвые. Т.к. мы мочим всё медленное и дорогое. В этом и цель моков (стабов) — удешевлять тесты.

P>>Для начала это вы сами сделали тесты дорогими, когда поместили БЛ прямо в контроллер
·>Ты опять скрываешь что ты понимаешь под дороговизной.

Я ж сказал — если я поменяю внутреннюю реализацию, никакие тесты падать не должны.
Мы покрываем ожидания вызывающей стороны, а у неё как раз нету ожиданий "вызовем crypto hmac вот таким макаром"
А у вас видение "раз hmac в требованиях, то нужны моки"
А следовательно, вам придется фиксить тесты -> удорожание

P>>Вы все еще про статью, тут я ничем помочь не могу.

·>getUser и toFields это не про статью.

Разумеется. Зачем продолжать мусолить заведомо однобокую статью?

·>У тебя было expect...toBeCalledWith — это было совершенно неправильное решение. Я просто попытался объяснить, что так делать не надо и показал как надо, сделав оговорку "Если не задаваться вопросом, зачем вообще вдруг понадобилось мокать hmac".


Ну ок — если вы не предлагаете мокать hmac, то всё в порядке.

P>>Не одна — для каждого из тестов вам нужен будет этот мок

·>Зачем для каждого? Этот мок можно куда-нибудь в setUp вынести.

Он будет везде использоваться. А раз так, то ваш мок надо или посать под каждый тест. Или запилить в ём поддержку всех кейсов.
Если под каждый тест — очевидно, дороже чем сравнение значений
Если один на всё — вы затачиваете тесты об синтетику

P>>hmac, time, итд. В одном файле — что бы чинить не 100 тестов, а всего 10 значений когда вы поменяете hmac 256 на 512.

·>Я может что-то не знаю, но обычно hmac зависит от каждого теста — контрольная сумма сериализованного тела ответа. У тебя в твоём хоумпейдже на всю функциональность хватает 10 тестов?

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

P>>1 При изменении алгоритма, формата итд, сломаются ваши моки

·>Не сломаются.

Вот пример — вы решили изменить сигнатуру getAll у вашего repository, добавить-убрать фильры-сортировку-итд.
getAll у вас покрыт моками.
Вопрос — надо ли их фиксить, если поменялась сигнатура getAll? Если нет — покажите это чудо примером

P>>Эта согласованность проверится любым интеграционным тестом который вызовет контроллер.

·>Не любым.

Покажите пример.

P>>Поздравляю, это классика тавтологических тестов

P>>2 вариант, кладем в файлик тестовы-данные.json "время", "правильный результат", штуки 10 строчк. Меняем дизайн на nextFriday(now) и теперь у нас простой юнит-тест
·>Ты подменил задание, а не дизайн. Вначале была функция nextFriday(), потом вдруг стала nextFriday(now). Если твоя система должна по контракту клиентам выдавать операцию nextFriday(), то поменять сигнатуру ты не можешь.

Разве я сказал, что nextFriday это функция системы? Это одна из операций в самой БЛ, вопрос только в том, как её тестировать — моками или без них

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

·>Верно. Но почему-то ты считаешь, что задачи из других классов не существует.

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

P>>2. если нет другого варианта, как в примере Буравчика, изолировать от зависимостей, что бы логика всё равно зависела от своих параметров.

·>У тебя зависимости не изолировались, просто перелилось из пустого в порожнее. Кода стало больше.

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

P>>Этот самый и-тест по любому нужен, для каждого do_login метода. Как бы вы не выкручивались. У меня метод do_logic будет линейным, заниматься только связыванием.

·>Не по любому. Я тебе уже рассказывал как.

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

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

·>Это твои фантазии. Судя по тому коду, который ты привёл, ты не понимаешь как пользоваться моками и придумываешь себе страшилки.

Пока что от вас ни одного примера не было.

P>>3. expect(buildRequest(params)).to.deep.eq(pattern)

·>Что за паттерн-то? Полный код можешь написать? Вот ты рассказал, что оно выглядит так:
·>
·>{
·>  sql: 'select * from users where id=?',
·>  params: [id],
·>  transformations: {
·>     in: [fn1],
·>     out: [fn2]
·>  }
·>}
·>

·>Какой код будет в тесте для проверки этого?

Вот, что нового вы здесь открыли для себя? const pattern = ?
const pattern = {
  sql: 'select * from users where id=?',
  params: [id],
  transformations: {
     in: [fn1],
     out: [fn2]
  }
}

expect(buildRequest(params)).to.deep.eq(pattern)


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

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

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

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

·>Что значит "не тот"? Как проверить тот он или не тот? Как в принципе узнать как должен выглядеть тот?

Для этого нужно знать, какие запросы кому вы отправляете. ORM — пишите тест на построение запроса к ORM. Если же ORM такого не умеет — вот он случай для косвенных проверок, и для моков.

P>>Много фильтров и их комбинаций, да еще и пагинация

·>И? зачем для этого несколько тысяч значений? У тебя там комбинаторно рвануло, да?

Смотря сколько фильтров и значений. Если у вас один филд и один простой фильтр <> — хватит десятка.

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

·>Пока ты продемонстрировал лишь добавление кода.

Вы пока вообще ничего не продемонстрировали.

P>>Затем, что самое сложное в БД это построение или запроса к ORM, или SQL. Слишком много запросов похожих на тот, что нам нужен, но нерабочих.

·>Построение запроса это детали реализации, важно что построенный запрос вернёт.

Нам нужны гарантии, что этот запрос не повалит нам прод, когда в него пойдут произвольные комбинации фильтров и всего остального

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

P>>Зато на тестовой бд всё хорошо
·>Если эти наборы параметров забыли протестировать, то чем твой getUser поможет?

Тут мы возвращаемся к тем нескольким тысячам значений. Нам нужны все значения в фильтрах, и всех их ркомбинации, =, <>, like итд.

P>>Если понятия не имеете, то скорее всего и запросы ваши будут работать приблизительно. Например, фильтры будут работать только на тестовых данных

·>У тебя тоже.

Я вам примеры беру из своей практики. На новом проекте именно такая проблема, я это как раз и исправляю.

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

·>Аудит это и есть бл. Аудит — прямое требование бизнеса. Ты ведь надеюсь, уже перестал путать логи и аудит?

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

P>>Процентов 30-40 от сборки.

·>А что потом после сборки?
После сборки — тесты.

P>>Вы что, в первой версии приложения знаете, какие фичи понадобятся в течении следующих 10 лет? АПИ всегда эволюционирует

·>Как правило, он расширяется, а не эволюционирует. И чем реже, тем лучше.

у вас вероятно только расширяется. Откройте любое взрослое апи — v1, v2, v3, deprecated итд.

P>>Если манифест держать кодом, то это хорошая заявочка на рассинхронизацию — у нас работает, а у клиентов — нет.

·>Я вообще за contract-first.

Вот аннотации и есть тот самый контракт — в нем указано всё, что надо. Кода еще нет, а интеграция уже готова и можно начинать одновременно писать и клиента, и тесты, и сам код бакенда.
Re[30]: Что такое Dependency Rejection
От: · Великобритания  
Дата: 28.12.23 13:20
Оценка:
Здравствуйте, Pauel, Вы писали:

P>>>·>И? С моками (стабами) тесты такие же дешёвые. Т.к. мы мочим всё медленное и дорогое. В этом и цель моков (стабов) — удешевлять тесты.

P>>>Для начала это вы сами сделали тесты дорогими, когда поместили БЛ прямо в контроллер
P>·>Ты опять скрываешь что ты понимаешь под дороговизной.
P>Я ж сказал — если я поменяю внутреннюю реализацию, никакие тесты падать не должны.
Т.е. если ты поменяешь 'select * from users where id=?' на 'select id, name, email from users where id=?' у тебя тесты и посыпятся.

P>Мы покрываем ожидания вызывающей стороны, а у неё как раз нету ожиданий "вызовем crypto hmac вот таким макаром"

Я уже показал как пишутся тесты с моками для hmac. Там не должно быть такого.

P>А у вас видение "раз hmac в требованиях, то нужны моки"

Ты врёшь.

P>>>Вы все еще про статью, тут я ничем помочь не могу.

P>·>getUser и toFields это не про статью.
P>Разумеется. Зачем продолжать мусолить заведомо однобокую статью?
Ок. Наконец-то ты согласился, что таки да, у тебя прод-кода больше. Нам такого не надо.

P>·>У тебя было expect...toBeCalledWith — это было совершенно неправильное решение. Я просто попытался объяснить, что так делать не надо и показал как надо, сделав оговорку "Если не задаваться вопросом, зачем вообще вдруг понадобилось мокать hmac".

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

P>>>Не одна — для каждого из тестов вам нужен будет этот мок

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

P>>>hmac, time, итд. В одном файле — что бы чинить не 100 тестов, а всего 10 значений когда вы поменяете hmac 256 на 512.

P>·>Я может что-то не знаю, но обычно hmac зависит от каждого теста — контрольная сумма сериализованного тела ответа. У тебя в твоём хоумпейдже на всю функциональность хватает 10 тестов?
P>А нам не надо везде тащить этот hmac и тестировать это в каждом тесте. юнит-тестами покрываем вычисление hmac по конкретному контенту, и всё. Остальное — в интеграционных, коих на порядок меньше чем юнит-тестов.
Что значит не надо? У тебя там deep equals и сравнение логов. А интеграционных у тебя тоже дофига, т.к. они покрывают каждый do_logic.

P>>>1 При изменении алгоритма, формата итд, сломаются ваши моки

P>·>Не сломаются.
P>Вот пример — вы решили изменить сигнатуру getAll у вашего repository, добавить-убрать фильры-сортировку-итд.
P>getAll у вас покрыт моками.
P>Вопрос — надо ли их фиксить, если поменялась сигнатура getAll? Если нет — покажите это чудо примером
Это типичный рефакторинг add-remove method parameter. Если добавляем парам, то тот прод-код, который мы меняем, ведь теперь надо передавать какое-то значение в такой параметр — надо и в тесте зафиксировать изменение в поведении. Удаление парама — так вообще тривиально, IDE автоматом везде подчистит, тест-не тест, не важно.

P>>>Эта согласованность проверится любым интеграционным тестом который вызовет контроллер.

P>·>Не любым.
P>Покажите пример.
Ну ты там ю-тест упоминал "а что если в колонке пусто". Вот такой соответствующий и-тест тоже придётся писать. Т.е. ю-тест бесполезен, ибо такое можно полноценно протестировать только и-тестом repo-dbms. Как я понял, ты здесь под и-тестом имеешь в виду тест вида "curl".

P>>>Поздравляю, это классика тавтологических тестов

P>>>2 вариант, кладем в файлик тестовы-данные.json "время", "правильный результат", штуки 10 строчк. Меняем дизайн на nextFriday(now) и теперь у нас простой юнит-тест
P>·>Ты подменил задание, а не дизайн. Вначале была функция nextFriday(), потом вдруг стала nextFriday(now). Если твоя система должна по контракту клиентам выдавать операцию nextFriday(), то поменять сигнатуру ты не можешь.
P>Разве я сказал, что nextFriday это функция системы? Это одна из операций в самой БЛ, вопрос только в том, как её тестировать — моками или без них
По сути это не столь важно. Если у тебя в бл будет nextFriday(now), то во всех контроллерах где зовётся этот метод придётся передавать туда это самое now. И придётся писать полные интеграционные тесты, что туда передаётся именно серверное текущее время, а не что-то другое.
Т.е. вместо одного легковесного ю-теста с моком для данного конкретного метода бл, нам теперь приходится писать туеву хучу тяжёлых и-тестов на каждый вызов этого метода из разных контроллеров. Ещё интересно послушать как именно ты собираешься в "curl" и-тестах ассертить, что туда передаётся именно текущее время.

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

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

P>>>2. если нет другого варианта, как в примере Буравчика, изолировать от зависимостей, что бы логика всё равно зависела от своих параметров.

P>·>У тебя зависимости не изолировались, просто перелилось из пустого в порожнее. Кода стало больше.
P>А вы свой пример покажите, что бы можно было тестировать на разных наборах данных-входов итд, вот и сравним.
Ну я не знаю что именно показать. Ну допустим хотим nextFriday() потестировать:
var clock = mock(InstantSource.class);
var testSubject = new CalendarLogic(clock);

when(clock.instant()).thenReturn(Instant.of("2023-12-28T12:27:00Z"));
assertThat(testSubject.nextFriday()).equalTo(LocalDate.of("2023-12-29"));

when(clock.instant()).thenReturn(Instant.of("2023-12-29T23:59:59Z"));
assertThat(testSubject.nextFriday()).equalTo(LocalDate.of("2023-12-29"));

when(clock.instant()).thenReturn(Instant.of("2023-12-30T00:00:00Z"));
assertThat(testSubject.nextFriday()).equalTo(LocalDate.of("2024-01-05"));


Я вполне допускаю, что внутри CalendarLogic отрефакторить nextFriday() так, чтобы оно звало какой-нибудь static nextFriday(now) который можно потестирвать как пюрешку, но зачем? Если у нас конечная цель иметь именно nextFriday(), то рисовать ещё один слой, увеличивать количество прод-кода ради "единственно верного дизайна" — только энтропию увеличивать.

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

P>·>Это твои фантазии. Судя по тому коду, который ты привёл, ты не понимаешь как пользоваться моками и придумываешь себе страшилки.
P>Пока что от вас ни одного примера не было.
Было несколько. Погляди сниппеты кода в моих сообщениях.

P>·>Какой код будет в тесте для проверки этого?

P>Вот, что нового вы здесь открыли для себя? const pattern = ?
Появилась возможность пальчиком тыкнуть в то, что у тебя в коде теста полный кусок текста запроса 'select * from users where id=?', а не какой-то магический паттерн, проверяющий что запрос "тот". Т.е. по большому счёту ты никак не можешь знать что запрос хотя бы синтаксически корректен. Пишутся такие тесты обычно копипастой текста запроса из прод-кода в тест-код и тестируют "как написано", а не ожидания. И валятся только после полной сборки и запуска всего приложения, нередко только в проде.

P>
P>     in: [fn1],
P>     out: [fn2]
P>expect(buildRequest(params)).to.deep.eq(pattern)
P>

Что такое "fn1" и "fn2"? Как deep.eq будет это сравнивать?

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

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

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

P>·>Что значит "не тот"? Как проверить тот он или не тот? Как в принципе узнать как должен выглядеть тот?
P>Для этого нужно знать, какие запросы кому вы отправляете. ORM — пишите тест на построение запроса к ORM. Если же ORM такого не умеет — вот он случай для косвенных проверок, и для моков.
Как это можно знать, что запрос "тот"? Единственный вменяемый вариант — выполнить запрос и поглядеть что вернулось. Полагаю ты делаешь это ручками, копипастишь текст запроса в консоль субд, вручную подставляешь парамы, выполняешь, глазками смотришь на результат, что похоже вроде бы на правду. Я предлагаю поручить это дело и-тестам repo+dbms. Пишем тест, он запускает построенный запрос с парамами и ассертит результат.

P>>>Много фильтров и их комбинаций, да еще и пагинация

P>·>И? зачем для этого несколько тысяч значений? У тебя там комбинаторно рвануло, да?
P>Смотря сколько фильтров и значений. Если у вас один филд и один простой фильтр <> — хватит десятка.
Не понял. Достаточно иметь одно значение и слать разные фильтры.
testSubject.save(new User(id, "Vasya")));
assertThat(testSubject.find(new Filter.byEqual("Vasya"))).isPresent();
assertThat(testSubject.find(new Filter.byNotEqual("Vasya"))).isEmpty();
assertThat(testSubject.find(new Filter.byEqual("Vasy"))).isEmpty();
assertThat(testSubject.find(new Filter.byLike("asy"))).isPresent();
...

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


P>·>Пока ты продемонстрировал лишь добавление кода.

P>Вы пока вообще ничего не продемонстрировали.
Я продемонстрировал как удалить код в твоём примере с hmac, например.

P>>>Затем, что самое сложное в БД это построение или запроса к ORM, или SQL. Слишком много запросов похожих на тот, что нам нужен, но нерабочих.

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

P>·>Если эти наборы параметров забыли протестировать, то чем твой getUser поможет?

P>Тут мы возвращаемся к тем нескольким тысячам значений. Нам нужны все значения в фильтрах, и всех их ркомбинации, =, <>, like итд.
Для этого нужны комбинации фильтров, а не комбинации значений.

P>>>Если понятия не имеете, то скорее всего и запросы ваши будут работать приблизительно. Например, фильтры будут работать только на тестовых данных

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

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

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

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

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

P>>>Процентов 30-40 от сборки.

P>·>А что потом после сборки?
P>После сборки — тесты.
Т.е. сборка без тестов 1.5 часа, потом ещё тесты час?

P>>>Вы что, в первой версии приложения знаете, какие фичи понадобятся в течении следующих 10 лет? АПИ всегда эволюционирует

P>·>Как правило, он расширяется, а не эволюционирует. И чем реже, тем лучше.
P>у вас вероятно только расширяется. Откройте любое взрослое апи — v1, v2, v3, deprecated итд.
v1,v2,v3 — это разные спеки, практически новое api.

P>>>Если манифест держать кодом, то это хорошая заявочка на рассинхронизацию — у нас работает, а у клиентов — нет.

P>·>Я вообще за contract-first.
P>Вот аннотации и есть тот самый контракт — в нем указано всё, что надо. Кода еще нет, а интеграция уже готова и можно начинать одновременно писать и клиента, и тесты, и сам код бакенда.
Аннотации — часть кода. Если вы захотите переписать свой сервис на другой ЯП, ну там с js на rust — что вы будете делать со своими аннотациями?
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[31]: Что такое Dependency Rejection
От: Pauel Беларусь http://blogs.rsdn.org/ikemefula
Дата: 30.12.23 13:43
Оценка:
Здравствуйте, ·, Вы писали:

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

·>Т.е. если ты поменяешь 'select * from users where id=?' на 'select id, name, email from users where id=?' у тебя тесты и посыпятся.

Посыплются. Есть такой недостаток. Потому нужно всегда писать осмысленные вещи,ш а не затыкать хренью. select * появится в том случае, если некто вместо построения запроса так и захардкодит.

P>>Мы покрываем ожидания вызывающей стороны, а у неё как раз нету ожиданий "вызовем crypto hmac вот таким макаром"

·>Я уже показал как пишутся тесты с моками для hmac. Там не должно быть такого.

Я шота не могу найти. Не могли бы вы ссылкой поделиться?

P>>А у вас видение "раз hmac в требованиях, то нужны моки"

·>Ты врёшь.

Очевидно, это я так вас понял. Вы слишком много таких аргументов тащите

P>>Разумеется. Зачем продолжать мусолить заведомо однобокую статью?

·>Ок. Наконец-то ты согласился, что таки да, у тебя прод-кода больше. Нам такого не надо.

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

P>>Он будет везде использоваться. А раз так, то ваш мок надо или посать под каждый тест. Или запилить в ём поддержку всех кейсов.

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

Я примерно так и имел ввиду.

P>>А нам не надо везде тащить этот hmac и тестировать это в каждом тесте. юнит-тестами покрываем вычисление hmac по конкретному контенту, и всё. Остальное — в интеграционных, коих на порядок меньше чем юнит-тестов.

·>Что значит не надо? У тебя там deep equals и сравнение логов. А интеграционных у тебя тоже дофига, т.к. они покрывают каждый do_logic.

У вас тут мешанина — интеграционных тестов по количеству методов в апи, + сценарии.
Сравнение логов только там, где вы это используете.
А чем deep equals не угодил я без понятия

P>>Вопрос — надо ли их фиксить, если поменялась сигнатура getAll? Если нет — покажите это чудо примером

·>Это типичный рефакторинг add-remove method parameter. Если добавляем парам, то тот прод-код, который мы меняем, ведь теперь надо передавать какое-то значение в такой параметр — надо и в тесте зафиксировать изменение в поведении. Удаление парама — так вообще тривиально, IDE автоматом везде подчистит, тест-не тест, не важно.

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

·>Ну ты там ю-тест упоминал "а что если в колонке пусто". Вот такой соответствующий и-тест тоже придётся писать.


Непонятно, что за проблему хотите решить этим дополнительным тестом. Перепроверить, что юнит-тест таки валидный?

·>По сути это не столь важно. Если у тебя в бл будет nextFriday(now), то во всех контроллерах где зовётся этот метод придётся передавать туда это самое now. И придётся писать полные интеграционные тесты, что туда передаётся именно серверное текущее время, а не что-то другое.

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

Количество тестов будет одинаковым. Только вам нужно будет везде указывать свой мок. Вы почему то верите, что моки избавляют от интеграционных. Это разные вещи. Интеграционный тест проверяет 1) что сама склейка валидная, а не 2) способность к ней. Моки проверяют второе, а нам надо — первое.

·>У тебя появляются "дешевые", но бесполезные ю-тесты, т.к. и-тесты всё равно нужны, практически в том же количестве.


В том то и дело, что тесты как раз полезные. А количество интеграционных зависит от апи и сценариев.

·>Ну я не знаю что именно показать. Ну допустим хотим nextFriday() потестировать:


Вот и сравните разницу
var testSubject = new CalendarLogic();
var now = Samples.nextFriday.case1.in; 

assertThat(testSubject.nextFriday(now)).equalTo(Samples.nextFriday.result);

В вашем случае точно так же нужно предусмотреть все кейсы, только у вас будет везде на 5 строчек больше

·>Я вполне допускаю, что внутри CalendarLogic отрефакторить nextFriday() так, чтобы оно звало какой-нибудь static nextFriday(now) который можно потестирвать как пюрешку, но зачем? Если у нас конечная цель иметь именно nextFriday(), то рисовать ещё один слой, увеличивать количество прод-кода ради "единственно верного дизайна" — только энтропию увеличивать.


Ну да, вы только и думаете в терминах моков

P>>·>Какой код будет в тесте для проверки этого?

P>>Вот, что нового вы здесь открыли для себя? const pattern = ?
·>Появилась возможность пальчиком тыкнуть в то, что у тебя в коде теста полный кусок текста запроса 'select * from users where id=?', а не какой-то магический паттерн, проверяющий что запрос "тот". Т.е. по большому счёту ты никак не можешь знать что запрос хотя бы синтаксически корректен. Пишутся такие тесты обычно копипастой текста запроса из прод-кода в тест-код и тестируют "как написано", а не ожидания. И валятся только после полной сборки и запуска всего приложения, нередко только в проде.

Расскажите, как вы вашими косвенными тестами дадите гарантию, что на проде никогда не случится ситуация, что запрос выродится в select * from users и у нас будут сотни тысяч записей вгружаться разом.
Подробнее пожалуйста.

Это вероятно вы тестируете чтото копируя чтото с прода. А я тестирую построение запроса, например, те самые фильтры. И логики в методе довольно много, т.к. в зависимости от разных фильтров у нас могут быть разные вещи.


P>>
P>>     in: [fn1],
P>>     out: [fn2]
P>>expect(buildRequest(params)).to.deep.eq(pattern)
P>>

·>Что такое "fn1" и "fn2"? Как deep.eq будет это сравнивать?

По ссылке.

P>>Ну вот выполняете запрос на проде, прод падает. Ваши действия?

·>Воспроизвожу сценарий в тесте и фиксю код.

Непросто код а в т.ч. и построение запроса к орм или бд.

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

·>Как это можно знать, что запрос "тот"? Единственный вменяемый вариант — выполнить запрос и поглядеть что вернулось.

Что вы так думаете — это я помню. Вы никак не расскажете, как побороть data complexity, т.е. как убедиться что ваш запрос не выдаст какой нибудь вырожденный кейс и вгрузит всё.

P>>Смотря сколько фильтров и значений. Если у вас один филд и один простой фильтр <> — хватит десятка.

·>Не понял. Достаточно иметь одно значение и слать разные фильтры.
·>
·>testSubject.save(new User(id, "Vasya")));
·>assertThat(testSubject.find(new Filter.byEqual("Vasya"))).isPresent();
·>assertThat(testSubject.find(new Filter.byNotEqual("Vasya"))).isEmpty();
·>assertThat(testSubject.find(new Filter.byEqual("Vasy"))).isEmpty();
·>assertThat(testSubject.find(new Filter.byLike("asy"))).isPresent();
·>...
·>

·>Ок... ну может для сортировки и пагинации может понадобиться чуть больше одного значения.

У вас здесь та самая data complexity. Вам нужно обосновать внятно, почему ваш фильтр никогда не вернет пустое выражение, и запрос не выродится в "вгрузить всё"
Кроме того, если фильтров много, то количество данных будет расти экспоненциально.

P>>Вы пока вообще ничего не продемонстрировали.

·>Я продемонстрировал как удалить код в твоём примере с hmac, например.

Я нашел только тот вариант, где вы мокаете hmac. У вас есть что лучше?

P>>Нам нужны гарантии, что этот запрос не повалит нам прод, когда в него пойдут произвольные комбинации фильтров и всего остального

·>И как ты эти гарантии собрался обеспечивать? Неиспользованием моков?

Здесь тесты не дают полного контроля над ситуацией. Нам нужно спроектировать запрос и доказать его корректность, а построение фильтров итд покрыть юнит-тестами.

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

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

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

P>>После сборки — тесты.

·>Т.е. сборка без тестов 1.5 часа, потом ещё тесты час?

Чото конкретно вас смущает?

P>>у вас вероятно только расширяется. Откройте любое взрослое апи — v1, v2, v3, deprecated итд.

·>v1,v2,v3 — это разные спеки, практически новое api.

Забавно, что ту часть, где deprecated вы скромно опустили. Вы так в слова играете?

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

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

Вы из какого века пишете? Если по аннотациям можно получить клиентский код, серверный, да еще и тесты с документацией, то почему нельзя для rust это проделать?
Смена стека технологий встречается куда реже, чем смена бд или изменения в апи.
Вы бы еще метеориты вспомнили
Отредактировано 30.12.2023 14:20 Pauel . Предыдущая версия .
Re[27]: Что такое Dependency Rejection
От: Pauel Беларусь http://blogs.rsdn.org/ikemefula
Дата: 30.12.23 14:37
Оценка:
Здравствуйте, Буравчик, Вы писали:

P>>Лямбда которая возвращает значения по списку


Б>Здесь я тоже не понял. Можно пример?


Не знаю, как это на питоне. Задаете список. Лямбда вызывает итератор этого списка. Всё.
Re[23]: Что такое Dependency Rejection
От: Pauel Беларусь http://blogs.rsdn.org/ikemefula
Дата: 30.12.23 14:51
Оценка:
Здравствуйте, Буравчик, Вы писали:

Б>Ты также привязываешься к конкретным вызовам, параметрам, последовательностям, но не моками, а лямбдами. Нет разницы в этом.


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

Б>Но ты усложняешь логику, добавляя лишние лямбды, которые просто оборачивают зависимости (как говорится, фабрика на фабрике, в твоем случае адаптер на адаптере).


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

P>> Потому полностью уйти от моков не выйдет.


Б>Если говорить про внешние зависимости, твои лямбды это и есть моки. Никуда от них не ушел.


Цитирую себя "Потому полностью уйти от моков не выйдет." Причина в зависимостях, т.е. ваш пример он почти идеально подходит под моки.

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


Б>Я стараюсь выбирать подходящий инструмент (более простой).


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

Б>Если я вижу сложную логику, то выделяю ее в отдельную функцию и тестирую как чистую функцию (входные параметры -> результат)

Б>Если я вижу много взаимодействия с внешними зависимостями, то использую моки. Это позволяет протестировать поведение как обычную функцию (входные параметры + "мок-данные из внешних систем" -> результат

Примерно так, да. Только взаимодействие со внешними зависимостями тоже разное бывает. Например, мы делаем так
1. вычисляем какие измнения-вызовы нужны, чистая часть, много логики
2. выполняем все нужные изменения-вызовы итд, грязная, обычно линейная
В вашем примере такой фокус провернуть не получится
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.