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

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

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

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

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

·>Частичное применение функции — даёт функцию.

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

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

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

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

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

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

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

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

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

Я вам вобщем то ровно то же говорю.

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

·>Проверять на что и каким тестом? В call site я могу использовать мок nextFriday() и проверять поведение ю-тестами.

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

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

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

Джава умеет пересечения, объединения типов, условную типизацию, алгебраические типы?
Ы-ы-ы — всё это придется затыкать юнит-тестами, компилер вам не помощник.

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

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

whitebox — это ваше желание протестировать каждый вызов функции.
blackbox — тестируем исходя из функциональных требований.

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

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

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

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

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

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

·>Компилятор расскажет.

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

P>>Ровно так же дела с переносом кода куда угодно, выделение наследника, сплющивание иерархии, конверсии класса в функцию и обратно

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

Ровно там, где у нас связывание, а таких мест
1 немного
2 код в них тривиальный, линейный

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

·>А ты можешь заинлайнить, например, хотя бы str.length()? Или тоже "тапочки не приносит"?

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

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


Это с любыми юнит-тестами так. Для того нам и нужна таблица истинности рядом с кодом и его тестами — такое можно только другими методами проверять
У вас будет ровно то же — только косвенные проверки и больше кода.

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

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

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

·>Это не буквально?!

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

P>>То есть, у нас в наличии обычные вычисления вида object -> sql которые идеально покрываются юнит-тестами

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

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

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

·>А у меня этих твоих do_logic — по нулю на юз-кейс. Чуешь разницу?

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

Функции приложения никакие юнит-тесты не проверяют. Функции компонента никакие юнит-тесты не проверяют. А вот вычисление nextFriday — ровно то, что нужно .

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

·>Ерунда какая-то.
·>Во-первых, это очень слабая проверка, т.к. такое работает если только у тебя _каждая_ функция имеет уникальный тип результата.

Это лучше, чем покрывать конское количество вычислений косвенными проверками

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

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

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

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

·>Да.

Вот вот.

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

·>Нет.

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

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

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

Для простых кейсов сгодится.

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

·>Но вот тут "params: [id], { [fn1],out: [fn2]}" ты именно это и тестируешь. Ты уж определись надо оно тебе или не надо... Или ты тестируешь то, что тебе не надо?!!

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

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

·>Ну да. Больше логики — больше тестов. Причём тут "косвенность" проверок?

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

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

·>Ты либо крестик, либо трусы. Док.программирование это спеки, контракты и верификаторы. Тесты тут непричём.

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

P>>А еще чаще это признак огромного проекта

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

Да, ваша телепатия похоже прогрессирует

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

·>Написать такой корявый манифест немного сложнее, чем сгенерить.

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

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

·>Тогда нафиг и не нужны аннотации, и уж тем более манифесты.

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

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

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

АПИ. Его формат непринципиален — интерфейс с аннотациями, json, idl, curl, абстрактные классы, просто набор функций. Главное, что бы реализации нет.

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


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

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


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

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

·>И на чём эта вся логика написана? На grpc, да?

На чем хотите. grpc возможно не всё умеет, я не сильно в ём понимаю, но принципиальных проблем не вижу.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.