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

P>>Это я вам пример привел, для чего вообще люди мокают, когда всё прекрасно тестируется и без моков.

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

Хорошесть инструмента это именно то как другие его понимают и умеют применять. Если код на моках в основном кривой — значит моки за 20 лет индустрия так и не осилила.

Вы вот сами путаете моки и стабы

> Это проблема ликбеза, а не моков. Ровно та же проблема есть в любых подходах тестирования.


Даем слово Фаулеру:

Mocks are objects pre-programmed with expectations which form a specification of the calls they are expected to receive


Видите — моки это про описание вызовов и связаные с этим ожидания

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

P>>А зачем вам эти AsHex, AsBytes? Это же просто преставление. У вас есть токен — это значение. Его можно создать парой-тройкой способов, и сохранить так же.

·>Именно. 3 способа создания x 3 способа представления — уже девять комбинаций. У тебя будет либо девять функций, либо своя обёртка над сторонним api, изоморфная по сложности тому же api.

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

P>> Отсюда ясно, что нет нужды плодить те функции, что вы предложили, а следовательно и тестировать их тоже не нужно.

·>Нужда идёт из кода, из требований. Если у тебя достаточно сложная система, у тебя будет там все 9 комбинаций, а то и больше.

Это у вас так будет, тут я охотно верю.

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

·>Количество может быть уменьшено, только если ты какое-то требование перестал тестировать.

Количество может быть уменьшено за счет подхода к дизайну.
Например, у нас есть Date, Time, DateTime, DateTimeOffset, LocalDateTime, Instant, Absolute, и целый набор всякой всячины.
Так вот, для создания DateTimeOffset нам не надо перебирать всё множество вариантов, как вы предлагаете
Можно свести ровно к одному DateTimeOffset.from({day, month, year, hour, minute, seconds, offset})
И каждый тип должен уметь две базовые операции
1. создание из примитивов
2. деструктурирование в примитивы

Соответсвенно вместо N! комбинаций нам надо протестировать всего 2N
А операция конверсии в прикладном коде будет вот такой DateTimeOffset.from({...date.toFields(), ...time.toFields, offset: serverOffset});

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

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

·>Что лично тебе я приписал?

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

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

·>А ты таки предложил новую абстракцию ввести в виде ApiToken.from.

ну да, ваше taraparam куда лучшая абстракция, не подкопаешься.

·>Именно. Зачем нам тогда нужен ApiToken.from?


Затем, зачем и ваш taraparam

P>>Тем не менее, пример на моках что я вам показал, он именно отсюда взят.

·>Пример на то и пример, чтобы показать как использовать моки. А в твоём примере было показано как _не надо_ писать код.

Именно — так не надо, но именно так пишут и это типичный пример, к сожалению

P>>А вот если вы замокали jsonwebtoken, то ваши тесты сломаются. И причина только в том, что вы замокали там где этого не надо делать.

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

Я вам привел конкретный пример — одну библиотеку для jwt заменили другой. Один клиент для бд, заменили другим. Один хттп клиент заменили другим...

P>>·>А какая разница? Ведь эту конструкцию надо будет тоже каким-то тестом покрыть.

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

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

P>>Там где логика — там юнит-тесты, без моков, а там где интеграция — там интеграционные тесты, тоже без моков.

·>Слишком тяжелый подход. Только на мелких приложениях работает.

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

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

·>Это как? То что зависимости неявные — это не означает, что их нет.

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

P>>А если этого не делать, то и моки не нужны.

·>Ага-ага. Но тогда нужны ещё более медленные и неуклюжие интеграционные тесты, много

Интеграционные всех уровней нужны всегда. Их количество определяется
1. апи
2. сценариями

Большой апи — будет много тестов апи, иначе никак.
Много сценариев — будет много тестов e2e, и этого тоже не избежать.

P>>Зачем global? DI можно по разному использовать — речь же об этом. Посмотрите framework as a detail или clean architecture, сразу станет понятнее.

·>require это нифига не DI, это SL

В огороде бузина, а в киеве дядька. При чем здесь require ? framework as a detail и clean architecture это подходы к дизайну, архитектуре.

P>>С вашими моками покрытие будет хуже, кода больше, а интеграционные всё равно нужны

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

Интеграционные тесты начинаются после самых простых юнит-тестов. компонентные тесты — это уже интеграционные. Функциональные — снова интеграционные. Апи — они же.
Приложение — снова они же. Система — снова интеграционные. E2E, acceptance — интеграционнее некуда.

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

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

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


P>>·>Это только для tryAcceptComposition. А таких врапперов придётся написать под каждый метод контроллера. И на каждый пару тестов.

P>>Именно! И никакие моки этого не отменяют!
·>Они отменяют необходимость написания тучи этих самых xxxComposition, нет кода — нечего тестировать.

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

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

·>Не весь, а только покрывающий интеграцию компонент.

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

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

·>Это какая-то специфичная проблема в твоих проектах, я не в курсе.

Покажите как вы в репозиторий метод getAll передаете фильтры, сортировку итд

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

·>Задача инеграционного теста — тестировать места взаимодействия компонент, а не сценарии поведения.

Именно — кто даст гарантию что с тем или иным конфигом, рантаймом, переменными окружения, тот самый метод контролера не начнет вдруг гарантировано валиться ?

·>Как ассертить такую функцию в юнит-тесте? Как проверить, что текст запроса хотя бы синтаксически корректен? И как ассертить что параметры куда надо положены и транформация верна?


expect(query).to.deep.eq(pattern)


·>И далее, ну допустим ты как-то написал кучу тестов что getUser выглядит правильно. А дальше? Как убедиться, что настоящая субд будет работать с getUser именно так, как ты описал в ожиданиях теста репы?


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

P>>Упадет интеграционный тест, тест вида "выполним операцию и проверим, что состояние юзера соответствует ожиданиям".

·>Плохо, это функциональная бизнес-логика, а не интеграция. Это должно ловиться юнит-тестом, а не интеграционным.

Это типичный пример интеграция — вы соединяете выходы одного со входами другого.

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

·>Т.е. нужно писать интеграционный тест как минимум для каждого метода контроллера. Это слишком дохрена.

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

P>>Я вам пишу про разные кейсы из того что вижу, а вы все пытаетесь причину во мне найти

·>Т.е. ты пытаешься своё "Я так вижу" за какой-то объективный факт.

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

P>>А моки вам зачем для этого?

·>Чтобы описать варианты результата что может вернуть modifyUser.

Это называется стаб, а не мок

·>Это наверное для js актуально, где всё есть Object и где угодно может быть что угодно. При статической типизации вариантов разной природы не так уж много. Если modifyUser возвращает boolean, то тут только два варианта данных. А вот в js оно внезапно может выдать "FileNotFound" — и приехали.


И в джаве, и в дотнете полно разновидностей — ждете одно, приходит того же типа, похоже на то что надо, но чуточку другое. Бу-га-га.
А там где используют object, то отличий от js никакого нет

P>>Это в любом случае так — e2e тесты должны покрыть весь входящий и исходящий трафик.

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

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

P>>Похоже, вы незнакомы с acceptance driven тестированием. В данном случае нам нужно просмотреть логи, и сделать коммит, если всё хорошо.

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

Вы там что, на каждый коммит всю систему переписываете?

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

·>Нет. Интеграционные тесты должны протестировать интеграцию, а не весь апи.

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

P>>Как ваш репозиторий фильтры принимает ?

·>Не очень знаком с этой терминологией.

Типичная функциональность —
дай всё по страницам по фильтру на дату отсортированым


Вот здесь пространство решений чудовищное.

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

·>Если изоляция нужна, можно использовать интерфейсы.

Нужно. И девелопера на такое тренировать очень долго.

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

·>Ну ликбез же.

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

·>Похоже это просто N-ая итерация очередной вариации самой правильной организации трёх слоёв типичного веб-приложения. MVC, MVP, MVI, MVVM, MVVM-C, VIPER, и ещё куча...

·>Причём тут моки — неясно.

Старый подход — контролер вызывает сервис БД который вызывает БЛ через репозиторий итд. Моками надо обрезать зависимости на БД у БЛ.
Т.е. БЛ это клиент к БД, по факту.

В новом подходе БЛ, модель не имеет зависимости ни на репозиторий, на на БД. А раз так, то и моки не нужны.
Т.е. в центре у нас БЛ, модель, а вызывающий код является её клиентом, и именно там зависимости на БД итд.
Т.е. у нас слои поменялись местами.

·>Я вообще в последние N лет редко занимаюсь UI/web. У меня всякая поточка, очереди, сообщения, low/zero gc и прочее. Как там этот getUser сделать с zero gc — тот ещё вопрос.

·>Моки же общий инструмент, от деталей архитектуры зависит мало.

Актуальность моков зависит от вашей архитектуры. Если мы делаем БЛ, модель без зависимостей к БД, то и моки не нужны для тестирования.

·>А что за версия яп? Ну не важно. Это ещё можно простить, это просто маппинг урлов на объекты при отсутствии схемы REST. Как туда запихать аудит? Как аудит-аннотация будет описывать что, куда, когда и как аудитить?


Покажите ваш типичный код с аудитом.

P>>Кое что надо и в самом методе вызывать, без этого работать не будет.

·>Именно! И накой тогда аннотация нужна для этого?

Базовые гарантии. Некоторые из них можно выключить или доопределить конфигом итд.

·>Строчки где видим? Аудит может сообщения куда-нибудь в сокет слать или в бд отдельную писать.

·>И где валидировать, что для данных сценариев записываются нужная инфа в аудит?

Разумеется, это интеграция — что аудит интегрирован с тем или иным стораджем. 1 тест.

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


Вот-вот. Только не ясно, чем вам мешают аннотации и зачем моки
Отредактировано 21.12.2023 13:41 Pauel . Предыдущая версия . Еще …
Отредактировано 21.12.2023 13:40 Pauel . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.