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

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

·>Вопрос я не понял, мол что делать если что-то меняется, ответил как мог — если что-то меняется, то эээ... надо что-то менять. Если тебя интересует конкретная проблема, сформулируй с примерами кода.

Вы мок затачивали на все тесты, или по моку на каждый?
1. На все — все ваши тесты неявно теперь зависят от мока. И если вы закостылили мок, то заточили тесты под синтетику, код — под такие тесты.
2. Если по моку на каждый — можно хоть ревью сделать. Кода больше, но и контроля больше.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Это тот, который вы демонстрируете

·>Связывание находится в wiring code. Контроллер это просто ещё один компонент, ничем принципиальным от других не отличается.


Связывание и есть wiring код. Основная обязанность контроллера — именно это. И тестировать связку, как следствие.
Если же вы сторонник жирных контроллеров, конечно же вам придется мокать всё на свете. Не только time, а еще и sin и cos

P>>Ы-ы-ы — всё это придется затыкать юнит-тестами, компилер вам не помощник.

·>Покажи код где там какой типизации не хватает в java. И не просто общие слова, а не забывай про контекст обсуждения.

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

P>>blackbox — тестируем исходя из функциональных требований.

·>И? "текущее время" vs "данный момент времени" — это функциональные требования. Причём тут вызовы функций?

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

P>>Вы сейчас в слова играете. Если не протаскивать зависимости, в CalendarLogic будет null во время вызова, буквально. Это значит, вы всю мелочевку повесите на dependency injection.

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

Сами спросили — сами ответили.

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

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

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

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

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

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

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

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

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

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

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

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

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

·>Это будет две функции. ЧПФ, слышал?

Я в курсе: "ассертим что вызываем с тем параметром а не этим"

P>>Ровно там, где у нас связывание, а таких мест

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

Не всё так страшно — мы упрощаем не только бл, но и связывание. Посколько у вас больше одного провайдера для зависимости, всё может играть по разному в зависимости от сценария.
В одном сценарии у вас один провайдер времени, в другом — другой. Опаньки! Только вы это размазали по коду.

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

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

Ну и что?

P>>Вы только что говорили о чудесной поддержке иде именно вашего дизайна. Покажите это чудо. Мне вот ИДЕ спокойно может заинлайнить любое из мест вызова nextFriday(now)

·>Круто, но зачем? Задачу-то какую решаем?

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

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


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

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

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

И не надо.

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

·>Это какой-то небуквальный выхлоп или что? Выхлоп какого билдера?

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

P>>Соответсвенно, и тестировать нужно соответствующим методом

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

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

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

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

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

P>>do_logic и есть или юз-кейс, или контроллер.

·>Я имею в виду вот этот твой do_logic. Это не юзкейс. Это код-лапша.

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


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

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

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

Должно, только ваш nextFriday поломал вообще всё, т.к. ему надо прописать другие значения, а без этого или все тесты красные, или зелены при гарантировано нерабочей системе.

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

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

Вам надо мок подфиксить, что бы время было той системы

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

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

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

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

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

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

P>>В интеграционных тестах мы не проверяем точки — мы проверяем саму сборку.

·>Если сборка идёт в трёх точках — проверить легко. Если сборка идёт в трёхсот точках — проверить сложно.

Вы почемуто забываете про dependency injection — это такой код, который труднее всего покрыть тестами, а между тем у него чудовищное влияние на систему.
Потому и стоит менять дизайн что бы это контролировать, а через di протаскивать только технологические зависимости.

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

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

Если это про некоторую сборку, когда вы проверяете результат, то все годится. Только вам здесь надо вбросить "время клиента" каким то раком. А потом проверить, что вбросили удачно.

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

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

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

·>

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

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

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

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

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

Похоже, что вам действительно ктото запретил метапрограммирование

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

·>Мрак.

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

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

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

Хорошего в этом как то маловато.

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

·>А конкретнее? С примером кода.

Это я вам вопрос задал, вообще говоря. Те самые фильтры — вполне себе хороший пример.

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

·>Зачем нам тестировать выхлоп билдера? У билдера должны быть свои тесты.

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

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

·>Не понимаю проблему. Если фильтр выродится в true, то субд вернёт те записи, которые мы не ожидаем, тест провалится.

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

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

·>Верификатор выдаст ошибку. Почитай про какой-нибудь coq что-ли.

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

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


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

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

·>Верно, но называется это code-first со всеми последствиями.

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

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

·>Апи будет просто набор интерфейсов и/или модели данных. С нулевой логикой.

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

Посмотрите grapql, grpc — API и есть код, вопрос в том, что вы деливерите. Полурабочий каркас приложения — это code-first. А если описание, которое могут взять другие команды, и работать независимо — design first

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

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

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


P>>Вы похоже не вдупляте — design-first = вначале деливерим АПИ, а потом уже реализация, куда входят переменные, конфиги, логика тенантов итд.

·>http-сервер это такой АПИ... Угу-угу.

Я вам про интерфейсы, а вы сюда http-server тащите. Дурака валяете.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.