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

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

P>·>Т.е. если ты поменяешь 'select * from users where id=?' на 'select id, name, email from users where id=?' у тебя тесты и посыпятся.
P>Посыплются. Есть такой недостаток. Потому нужно всегда писать осмысленные вещи,ш а не затыкать хренью. select * появится в том случае, если некто вместо построения запроса так и захардкодит.
Именно, и это просто один из примеров. Тысячи их. Небольшое изменение стиля во внутренней реализации — и твои безмокные тесты сыплются. И см. что ты заявил выше, подчёркнутое. У меня, кстати, такие проблемы только в твоих фантазиях.

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

P>·>Я уже показал как пишутся тесты с моками для hmac. Там не должно быть такого.
P>Я шота не могу найти. Не могли бы вы ссылкой поделиться?
Тут
Автор: ·
Дата: 06.12.23
.

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

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

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

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

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

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

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

Эээ. "Это" что? Логи, обычно, используются везде.

P>А чем deep equals не угодил я без понятия

Тем, что он сравнивает очень много. А не то, что конкретно тест тестирует.

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

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

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

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

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

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

Так как именно ты собираешься в "curl" и-тестах ассертить, что туда передаётся именно текущее время?

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

P>В том то и дело, что тесты как раз полезные. А количество интеграционных зависит от апи и сценариев.
В чём полезность? В том, что посыпаться могут от малейших изменений?

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

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

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

P>В вашем случае точно так же нужно предусмотреть все кейсы, только у вас будет везде на 5 строчек больше
Каких ещё пять строчек? Разница пока в одной строчке, на все тесты, не надо mock(InstantSource.class) делать. Если ты не понял, у меня было три различных тестовых сценария, для трёх разных now, у тебя — один.
И ты как всегда "забыл" что теперь тебе помимо _одной_ сэкономленной строчки в тест-коде придётся писать ещё по отдельному тесту на каждое место склейки nextFriday и now. Т.е. одну строчку экономишь и сотню добавляешь.

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

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

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

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

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

Как ты тестируешь что построенный запрос хотя бы синтаксически корректен?

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

P>·>Что такое "fn1" и "fn2"? Как deep.eq будет это сравнивать?
P>По ссылке.
По ссылке куда? Неужели fn1/fn2 — публично выставленные внутренности? И как тогда ты будешь знать, что нужно именно fn2, а не fn3?

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

P>·>Воспроизвожу сценарий в тесте и фиксю код.
P>Непросто код а в т.ч. и построение запроса к орм или бд.
Не знаю что за такой "непросто код". Код он и в африке код. Чем код построения запроса принципиально отличается от другого кода?

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

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

На вопрос "Как это можно знать, что запрос "тот"?" ты так и не ответил.

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

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

P>Кроме того, если фильтров много, то количество данных будет расти экспоненциально.

Почему данных-то? Просто разные фильтры для тех же данных.

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

P>·>Я продемонстрировал как удалить код в твоём примере с hmac, например.
P>Я нашел только тот вариант, где вы мокаете hmac. У вас есть что лучше?
Так говорил же — ну не мокать hmac, неясно зачем его вообще мокать. Тесты от этого медленнее не становятся.

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

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

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

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

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

P>·>Т.е. сборка без тестов 1.5 часа, потом ещё тесты час?
P>Чото конкретно вас смущает?
Слишком долго.

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

P>·>v1,v2,v3 — это разные спеки, практически новое api.
P>Забавно, что ту часть, где deprecated вы скромно опустили. Вы так в слова играете?
Дык deprecated это часть "только расширяется". Что старое не удаляется, а добавляется новое.

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

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

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

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

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

P>>·>Я уже показал как пишутся тесты с моками для hmac. Там не должно быть такого.
P>>Я шота не могу найти. Не могли бы вы ссылкой поделиться?
·>Тут
Автор: ·
Дата: 06.12.23
.


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

> Твои "{...toFields}" — тоже лишний код, и это всё прод-код.". Ты только от первого открестился, а на вторые два сделал вид, что их не было.


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


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

P>>Я примерно так и имел ввиду.
·>Почему тогда под "каждый тест" или "всех кейсов"?

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

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


Вы уже показали — кода в тестах в вашем исполнении больше.

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


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

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

·>Эээ. "Это" что? Логи, обычно, используются везде.

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

P>>А чем deep equals не угодил я без понятия

·>Тем, что он сравнивает очень много. А не то, что конкретно тест тестирует.

deep equals проверяет значение, всё разом, если у вас чтото больше чем инт или строка

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

·>Почему ломать? Вот был у тебя nextFriday(now), и вдруг теперь ты это меняешь на nextFriday(now, tz) — у тебя весь код как-то магически будет работать как раньше? Не верю.

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

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

·>Что юнит тест тестит не "как написано в коде", а некое реально возможное ожидание

Тест проверяет построение запроса. Построение запроса зависит от многих параметров — вот это связывание надо как то проверить.
Косвенные проверки, как вы любите, работают в простых кейсах типа key-value
Если у вас будет подсчет статистики, вы вспотеете покрывать тестами "из базы"

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

·>Не моки избавляют, а дизайн. Если ты себя заставил использовать nextFriday(now), то у тебя будет куча мест склейки, которые теперь _приходится_ тестировать дополнительными тестами.

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

·>Так как именно ты собираешься в "curl" и-тестах ассертить, что туда передаётся именно текущее время?


Никак.

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

P>>В том то и дело, что тесты как раз полезные. А количество интеграционных зависит от апи и сценариев.
·>В чём полезность? В том, что посыпаться могут от малейших изменений?

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

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

P>>Вот и сравните разницу
 var testSubject = new CalendarLogic();

 assertThat(testSubject.nextFriday(Samples.nextFriday.case1.in)).equalTo(Samples.nextFriday.case1.result);
 assertThat(testSubject.nextFriday(Samples.nextFriday.case2.in)).equalTo(Samples.nextFriday.case2.result);
 assertThat(testSubject.nextFriday(Samples.nextFriday.case3.in)).equalTo(Samples.nextFriday.case3.result);


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

·>Каких ещё пять строчек? Разница пока в одной строчке, на все тесты, не надо mock(InstantSource.class) делать. Если ты не понял, у меня было три различных тестовых сценария, для трёх разных now, у тебя — один.

ок — на 4.

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

·>Опять фантазии.

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

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

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

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

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

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

тот самый интеграционный тест, на некорретном запросе он упадет

P>>По ссылке.

·>По ссылке куда? Неужели fn1/fn2 — публично выставленные внутренности? И как тогда ты будешь знать, что нужно именно fn2, а не fn3?

а как вы знаете, синус нуля это 0 ? fn1 и fn2 должны быть видны в тестах, а не публично

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

·>Не знаю что за такой "непросто код". Код он и в африке код. Чем код построения запроса принципиально отличается от другого кода?

Тем, что у вас в тестовой бд может не оказаться тех самых значений, которые ломают один из сотен-тысяч кейсов

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

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

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

·>На вопрос "Как это можно знать, что запрос "тот"?" ты так и не ответил.


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

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

P>>У вас здесь та самая data complexity. Вам нужно обосновать внятно, почему ваш фильтр никогда не вернет пустое выражение, и запрос не выродится в "вгрузить всё"
·>А почему ваш не выродится?

Например, на это тест написан, который гарантирует, что никакой ввод не дает пустой комбинации.

P>>Кроме того, если фильтров много, то количество данных будет расти экспоненциально.

·>Почему данных-то? Просто разные фильтры для тех же данных.

И вы уверены, что в тестовой бд всегда будет полный фарш? Вы что, один работаете?

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

·>Так говорил же — ну не мокать hmac, неясно зачем его вообще мокать. Тесты от этого медленнее не становятся.

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

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

·>Противоречивые параграфы детектед.

Противоречие у вас в голове. Сказано — полного контроля не дают.

·>Как доказывать корректность запроса?


Да как обычно это делается с алгоритмами.

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

·>Мест снаружи — больше, чем одно место внутри. И каждое место снаружи надо покрывать отдельным тестом. А для одного места внутри будет один тест.

Вы себя превзошли — по вашему, если все сунуть в один main, понадобится ровно один тест

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

·>Слишком долго.

Откуда у вас знание "слишком" или "не слишком" ? Вы даже не в курсе, что за проект, но делаете какие то суждения. Для того проекта — нет, не слишком.

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

·>Можно. Но придётся и все эти аннотации как-то переписывать.

Переписывать то зачем? Чем они вам мешают?

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

P>>Вы бы еще метеориты вспомнили
·>Смотря как долго проект живёт.

Чем дольше, тем меньше шансов на переписывание, проще заново напилить новое.
Re[34]: Что такое Dependency Rejection
От: · Великобритания  
Дата: 01.01.24 01:18
Оценка: -1
Здравствуйте, Pauel, Вы писали:

P>·>Тут
Автор: ·
Дата: 06.12.23
.

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

>> Твои "{...toFields}" — тоже лишний код, и это всё прод-код.". Ты только от первого открестился, а на вторые два сделал вид, что их не было.

P>{...toFields} экономит вам десяток другой методов и тестов для них. Если у вас нет такого метода, вам придется или по месту колбасить код, намного больше, или создавать все возможные методы для конверсии.
Не экономит. Конкретные типы и конструкторы с ними соответствуют конкретным арифметическим операциям с календарём. Твоё toFields — какое-то месиво, неясно о чём и чревато багами. Скажем, если у тебя тип time окажется какого-то не того типа, то эта твоя ... начнёт давать странное месиво с неясным результатом из=за перекрытия одноимённых полей. Не надо так делать.

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

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

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

P>Вы уже показали — кода в тестах в вашем исполнении больше.
Больше, т.к. код делает больше.

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

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

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

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

P>>>А чем deep equals не угодил я без понятия

P>·>Тем, что он сравнивает очень много. А не то, что конкретно тест тестирует.
P>deep equals проверяет значение, всё разом, если у вас чтото больше чем инт или строка
Я знаю. И это плохо, особенно для юнит-тестов.

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

P>·>Почему ломать? Вот был у тебя nextFriday(now), и вдруг теперь ты это меняешь на nextFriday(now, tz) — у тебя весь код как-то магически будет работать как раньше? Не верю.
P>Поломаются тесты не там, где вы обмокали, а ровно по месту — меняется таблица истинности функции
Так функция может использоваться из множества мест.

P>В вашем случае далеко не факт, что найдете в моках все нужные места, и сделаете что бы соответствовало

Какие места? Зачем их искать?

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

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

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

P>·>Не моки избавляют, а дизайн. Если ты себя заставил использовать nextFriday(now), то у тебя будет куча мест склейки, которые теперь _приходится_ тестировать дополнительными тестами.
P>Не дополнительными, а теми же самыми интеграционными, что есть и у вас. Интеграционные тесты не пишутся на все вещи вида "проверим что где то внутре передается параметр". Если такое критично — выносится в юниты.
Ты не в тему споришь. Ты заявил "Интеграционный тест проверяет 1) что сама склейка валидная". Мест склейки now и nextFriday(now) у тебя будет полно. У меня такое место — одно. Какие такие "внутре передается параметр" ты имеешь в виду — я понятия не имею.

P>Если у нас код контролера линейный, а в моем случае это именно так, то нам нужен ровно один интеграционный тест, который нужен и вам

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

P>·>Так как именно ты собираешься в "curl" и-тестах ассертить, что туда передаётся именно текущее время?

P>Никак.
Именно. Т.е. усложняешь прод-код, теряешь возможность протестировать и ловишь баги в проде. Нам такого не надо.

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

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

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

P>>>Вот и сравните разницу
P>
P> var testSubject = new CalendarLogic();

P> assertThat(testSubject.nextFriday(Samples.nextFriday.case1.in)).equalTo(Samples.nextFriday.case1.result);
P> assertThat(testSubject.nextFriday(Samples.nextFriday.case2.in)).equalTo(Samples.nextFriday.case2.result);
P> assertThat(testSubject.nextFriday(Samples.nextFriday.case3.in)).equalTo(Samples.nextFriday.case3.result);
P>

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

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

P>·>Каких ещё пять строчек? Разница пока в одной строчке, на все тесты, не надо mock(InstantSource.class) делать. Если ты не понял, у меня было три различных тестовых сценария, для трёх разных now, у тебя — один.
P>ок — на 4.
Я тоже так умею:
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"));

Сейчас начнёшь символы считать, да?

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

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

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

P>>>Подробнее пожалуйста.
P>·>Начни с себя, как ты с вашими супер-тестами дадите гарантию.
P>И не собираюсь — data complexity решается совсем другими инструментами. Количество перестановок в фильтрах спокойно может исчисляться чудовищным количеством кейсов. Если некто поправит запрос, и забудет нагенерерить сотню другую данных — приплыли.
Именно. Так что неясно зачем ты делаешь эти предъявы о гарантиях. У тебя аргументация вида "Раз ваш подход не умеет кофе варить и тапочки приносить — значит это плохо и надо делать как фаулер завещал".

P>т.е. вы снова притягиваете решение которое зависит от человеческого фактора

А у тебя есть решение независящее от человеческого фактора?

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

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

P>>>По ссылке.

P>·>По ссылке куда? Неужели fn1/fn2 — публично выставленные внутренности? И как тогда ты будешь знать, что нужно именно fn2, а не fn3?
P>а как вы знаете, синус нуля это 0 ?
Я это знаю из тригонометрии, от моего кода не зависящей. А вот то, что fn2 — правильно, а fn3 — неправильно ты знаешь только из "как в коде написано". Т.е. твой код тестирует самого себя, порочный круг называется.

P>fn1 и fn2 должны быть видны в тестах, а не публично

А тесты должны, по уму, тестировать публичный контракт. @VisibleForTesting — это очень хороший сигнал code smells.

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

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

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

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

P>·>На вопрос "Как это можно знать, что запрос "тот"?" ты так и не ответил.

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

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

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

P>>>Кроме того, если фильтров много, то количество данных будет расти экспоненциально.

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

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

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

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

P>·>Противоречивые параграфы детектед.
P>Противоречие у вас в голове. Сказано — полного контроля не дают.
Именно. Зачем же тогда покрыть юнит-тестами?

P>·>Как доказывать корректность запроса?

P>Да как обычно это делается с алгоритмами.
И что мешает это же делать в repo+dbms?

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

P>·>Мест снаружи — больше, чем одно место внутри. И каждое место снаружи надо покрывать отдельным тестом. А для одного места внутри будет один тест.
P>Вы себя превзошли — по вашему, если все сунуть в один main, понадобится ровно один тест
Нет. Ты что-то не так прочитал. Перечитай что я написал. Под местом я понимал call site. У меня один call site, который склеивает календарную логику с текущим временем и потом во всех call site пишется просто nextFriday(). У тебя таких мест склейки будет в call site каждого nextFriday(now). ЧПФ, помнишь?

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

P>·>Слишком долго.
P>Откуда у вас знание "слишком" или "не слишком" ? Вы даже не в курсе, что за проект, но делаете какие то суждения. Для того проекта — нет, не слишком.
Это значит, что изменение в коде занимает как минимум день для выкатки в прод. Значит глобально архитектура выбрана плохо. Возможно надо резать на независимые компоненты.

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

P>·>Можно. Но придётся и все эти аннотации как-то переписывать.
P>Переписывать то зачем? Чем они вам мешают?
Тем, что они написаны на конкретном ЯП.

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

P>>>Вы бы еще метеориты вспомнили
P>·>Смотря как долго проект живёт.
P>Чем дольше, тем меньше шансов на переписывание, проще заново напилить новое.
Но при сохранении контракта, который у вас описан на аннотациях конкретного ЯП. В общем гугли ликбез, например, тут https://swagger.io/blog/code-first-vs-design-first-api/
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[35]: Что такое Dependency Rejection
От: Pauel Беларусь http://blogs.rsdn.org/ikemefula
Дата: 01.01.24 10:09
Оценка:
Здравствуйте, ·, Вы писали:

>>> Твои "{...toFields}" — тоже лишний код, и это всё прод-код.". Ты только от первого открестился, а на вторые два сделал вид, что их не было.

P>>{...toFields} экономит вам десяток другой методов и тестов для них. Если у вас нет такого метода, вам придется или по месту колбасить код, намного больше, или создавать все возможные методы для конверсии.
·>Не экономит. Конкретные типы и конструкторы с ними соответствуют конкретным арифметическим операциям с календарём. Твоё toFields — какое-то месиво, неясно о чём и чревато багами. Скажем, если у тебя тип time окажется какого-то не того типа, то эта твоя ... начнёт давать странное месиво с неясным результатом из=за перекрытия одноимённых полей. Не надо так делать.

Ваша альтернатива — понасоздавать вагон методов для конверсии "всё во всё" что дает вагон кода и вагон тестов. Или же вместо однострочной конверсии у вас будет несколько строчек кода точно так же без полного контроля.

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

·>Недостаточно для чего? Почему недостаточно? Причём тут таблица истинности?

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

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

·>Больше, т.к. код делает больше.

Функционально — ровно столько же.

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

·>Меньше интеграционных за счёт того, что они тестируют интеграцию. А не всю копипасту, которую ты предлагаешь плодить.

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

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

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

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

P>>deep equals проверяет значение, всё разом, если у вас чтото больше чем инт или строка

·>Я знаю. И это плохо, особенно для юнит-тестов.

Простите, вы мапперы из одной структуры в другую как тестировать собираетесь? А парсинг? А как вы проверите, что ваш репозиторий на конкретный запрос вернет ту самую структуру? Например, нам нужен order + order items + customer + delivery + billing
Как вы собираетесь протестировать результат?

P>>Поломаются тесты не там, где вы обмокали, а ровно по месту — меняется таблица истинности функции

·>Так функция может использоваться из множества мест.

Пусть используется. Что вас смущает ?

P>>В вашем случае далеко не факт, что найдете в моках все нужные места, и сделаете что бы соответствовало

·>Какие места? Зачем их искать?

Читайте себя "Так функция может использоваться из множества мест."

У вас в тех самых тестах все будет обмокано. Как узнаете, что там, в моках, должно вызываться не "то", а "это"?

P>>Если у вас будет подсчет статистики, вы вспотеете покрывать тестами "из базы"

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

Наоборот — именно для таких мелочей юнит-тесты и нужны.

·>Я эту багу поймаю на и-тесте repo+dbms — пытаясь воспроизвести пустую колонку.


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

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

·>Ты не в тему споришь. Ты заявил "Интеграционный тест проверяет 1) что сама склейка валидная". Мест склейки now и nextFriday(now) у тебя будет полно. У меня такое место — одно.

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

P>>Если у нас код контролера линейный, а в моем случае это именно так, то нам нужен ровно один интеграционный тест, который нужен и вам

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

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

P>>Никак.

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

Забавная телепатия.

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

·>Это страшилки.

Это следует из ваших примеров.


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


Тест на каждый вызов это классика тавтологичного тестирования

P>>ок — на 4.

·>Я тоже так умею:
·>
·>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"));
·>

·>Сейчас начнёшь символы считать, да?

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

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

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

Ну ок.

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

·>Именно. Так что неясно зачем ты делаешь эти предъявы о гарантиях. У тебя аргументация вида "Раз ваш подход не умеет кофе варить и тапочки приносить — значит это плохо и надо делать как фаулер завещал".

Вы забыли, что именно вы претендуете что решите это всё тестами.

P>>т.е. вы снова притягиваете решение которое зависит от человеческого фактора

·>А у тебя есть решение независящее от человеческого фактора?

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

P>>тот самый интеграционный тест, на некорретном запросе он упадет

·>Т.е. у тебя интеграционные тесты будет тестировать всё "чудовищное количество кейсов"? Накой тогда ю-тесты?

Интеграционные тесты не проверяют варианты склейки — они проверяют функционирование в сборе. Юнит-тесты проверяют работоспособность примитивов.

·>Я это знаю из тригонометрии, от моего кода не зависящей. А вот то, что fn2 — правильно, а fn3 — неправильно ты знаешь только из "как в коде написано". Т.е. твой код тестирует самого себя, порочный круг

называется.

Не из кода, а из требований вызывающей стороны.

P>>fn1 и fn2 должны быть видны в тестах, а не публично

·>А тесты должны, по уму, тестировать публичный контракт. @VisibleForTesting — это очень хороший сигнал code smells.

Это ваша фантазия — эдак у вас юнит-тесты станут code smell, тк они принципиально по своей сути видят слишком много.

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

·>И у вас в тестах может не оказаться тех самых значений для построения того самого ломающего запроса. Опять "тапочки не приносит"?

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

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

·>Нам — не надо.

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

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

·>Ага. Именно то самое whitebox, котором ты ещё недавно меня попрекал.

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

P>>И вы уверены, что в тестовой бд всегда будет полный фарш? Вы что, один работаете?

·>"Тапочки не приносит".

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

P>>Противоречие у вас в голове. Сказано — полного контроля не дают.

·>Именно. Зачем же тогда покрыть юнит-тестами?

Затем, что доказательство корректности — это необходимое условие. А такое доказательство + тесты + код ревью, необходимое и достаточное.

P>>·>Как доказывать корректность запроса?

P>>Да как обычно это делается с алгоритмами.
·>И что мешает это же делать в repo+dbms?

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

P>>Откуда у вас знание "слишком" или "не слишком" ? Вы даже не в курсе, что за проект, но делаете какие то суждения. Для того проекта — нет, не слишком.

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

Нет, не значит.
Вы не в курсе, ни что за проект, ни какие требования, итд. Далеко не все проекты могут протаскивать изменения в прод прям сразу.
Во вторых, я вам сказал, что это полная сборка, все платформы, всё-всё-всё. Кто вам сказал, что нет сборки отдельных модулей?
И так у вас всё — вместо вопросов занимаетесь телепатией.

P>>Переписывать то зачем? Чем они вам мешают?

·>Тем, что они написаны на конкретном ЯП.

Я ж сказал — если по аннотации можно сгенерировать 4 артефакта, то почему нельзя сгенерировать 5й, 6й, 7й?
Вы же видите только переписывание

P>>·>Смотря как долго проект живёт.

P>>Чем дольше, тем меньше шансов на переписывание, проще заново напилить новое.
·>Но при сохранении контракта, который у вас описан на аннотациях конкретного ЯП. В общем гугли ликбез, например, тут https://swagger.io/blog/code-first-vs-design-first-api/

Вы путаете code-first и design-first. design first — означает, что первое деливери это апи, а не реализация. code-first — наоборот. А какой файл вы в начале редактируете, json или java и yaml, дело десяток
Я ж сразу сказал — апи уже есть, а реализации нету, зато можно и тесты писать, и клиентский код, и документацию прикручивать
Отредактировано 01.01.2024 11:49 Pauel . Предыдущая версия .
Re[36]: Что такое Dependency Rejection
От: · Великобритания  
Дата: 01.01.24 13:49
Оценка: -2
Здравствуйте, Pauel, Вы писали:

P>·>Не экономит. Конкретные типы и конструкторы с ними соответствуют конкретным арифметическим операциям с календарём. Твоё toFields — какое-то месиво, неясно о чём и чревато багами. Скажем, если у тебя тип time окажется какого-то не того типа, то эта твоя ... начнёт давать странное месиво с неясным результатом из=за перекрытия одноимённых полей. Не надо так делать.

P>Ваша альтернатива — понасоздавать вагон методов для конверсии "всё во всё" что дает вагон кода и вагон тестов. Или же вместо однострочной конверсии у вас будет несколько строчек кода точно так же без полного контроля.
Моя альтернатива аккуратно реализовать реальную модель календаря с аккуратной арифметикой различных типов. Конверсия "всё во всё" — это полное непонимание предметной области.

P>·>Недостаточно для чего? Почему недостаточно? Причём тут таблица истинности?

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

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

P>·>Больше, т.к. код делает больше.
P>Функционально — ровно столько же.
Нет. Он у меня ещё и содержит склейку. У тебя склейки нет.

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

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

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

Могут, т.к. большинство логики тестируется ю-тестами. А и-тесты нужны только чтобы тестировать интеграцию уже протестированных компонент.

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

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

P>Склейка или нет дело десятое — вызывающая сторона никаких ожиданий на этот счет не имеет, ей про это неизвестно.

P>Проверка конкретной склейки — это вайтбокс, тест "как написано", классика тавтологии.
P>Вместо проверки конкретной склейки делаем интеграционный код линейным, буквально, а интеграцию осуществляем на самом верху.
Чем выше, тем больше мест. Функция nextFriday() осуществляет склейку у себя внутри. И покрывается одним тестом. У тебя будет nextFriday(now) — и на самом верху будет множество call site для этого накопипастено. Код линейный, да, но накопипастен в кучу мест.

P>>>deep equals проверяет значение, всё разом, если у вас чтото больше чем инт или строка

P>·>Я знаю. И это плохо, особенно для юнит-тестов.
P>Простите, вы мапперы из одной структуры в другую как тестировать собираетесь?
В ю-тестах — по частям. Иначе изменение логики маппинга одного поля сломает все deep.equal тесты, где это поле есть.

P>>>Поломаются тесты не там, где вы обмокали, а ровно по месту — меняется таблица истинности функции

P>·>Так функция может использоваться из множества мест.
P>Пусть используется. Что вас смущает ?
Что в каждое из этих мест тебе понадобится копипастить склейку nextFriday(now) с источником текущего времени и покрывать это ещё одним тестом. Хотя, как я понял, ты вообще заявил, что такое протестировать ты просто не сможешь.

Ещё раз. nextFriday()nextFriday(now)timeSource.now(). Если ты отказываешься использовать в call site готовый и протестированный nextFriday(), значит тебе в каждый call site придётся протаскивать timeSource и копипастить композицию с nextFriday(now). Это увеличивает кол-во прод-кода и требует больше более тяжеловесных тестов для покрытия всей копипасты.

P>>>В вашем случае далеко не факт, что найдете в моках все нужные места, и сделаете что бы соответствовало

P>·>Какие места? Зачем их искать?
P>Читайте себя "Так функция может использоваться из множества мест."
И?

P>У вас в тех самых тестах все будет обмокано. Как узнаете, что там, в моках, должно вызываться не "то", а "это"?

Эээ.. Ты рефакторингом точно умеешь пользоваться? Компилятор и IDE всё покажет, подскажет и подправит.

P>>>Если у вас будет подсчет статистики, вы вспотеете покрывать тестами "из базы"

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

P>·>Я эту багу поймаю на и-тесте repo+dbms — пытаясь воспроизвести пустую колонку.

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

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

P>·>Ты не в тему споришь. Ты заявил "Интеграционный тест проверяет 1) что сама склейка валидная". Мест склейки now и nextFriday(now) у тебя будет полно. У меня такое место — одно.
P>Интеграционный тест проверяет всю интеграцию разом, а вас тянет проверить каждую строчку. Каждая строчка — это про юнит-тестирование.
Твой один интеграционный тест проверяет интеграцию одного do_logic. Таких do_logic у тебя будет дофига, и каждого надо будет проверить.

P>>>Если у нас код контролера линейный, а в моем случае это именно так, то нам нужен ровно один интеграционный тест, который нужен и вам

P>·>_Такой_ интеграционный тест мне не нужен. Я могу обойтись одним ю-тестом с моком о том, что nextFriday() действительно возвращает правильные даты для разных моментов времени. И ещё соответсвующими ю-тестами в местах использования nextFriday(), там можно моком просто возвращать хоть семь пятниц на неделе.
P>Какой такой? Вам всё равно нужен интеграционный тест. Задачи интеграционных тестов не каждую склейку проверять по отдельности, а проверять работает ли компонент собраный целиком. Что там унутре — для интеграционного тестирования по барабану — нужная функция работает, значит всё хорошо.
Интеграционный тест чего?

P>>>Никак.

P>·>Именно. Т.е. усложняешь прод-код, теряешь возможность протестировать и ловишь баги в проде. Нам такого не надо.
P>Забавная телепатия.
Никакая не телепатия. Если ты никак не можешь протестировать, то проблемы будут обнаруживаться в проде. Ок, возможно, ещё при ручном тестировании.

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

P>·>Это страшилки.
P>Это следует из ваших примеров.
Не следует.

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

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

P>·>Сейчас начнёшь символы считать, да?

P>Обычное правило — 1 инструкция на строку. Вы втюхали минимум две.
И что? Небо на землю упадёт?

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

Ну ввести константу — не проблема.

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

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

P>>>т.е. вы снова притягиваете решение которое зависит от человеческого фактора

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

P>>>тот самый интеграционный тест, на некорретном запросе он упадет

P>·>Т.е. у тебя интеграционные тесты будет тестировать всё "чудовищное количество кейсов"? Накой тогда ю-тесты?
P>Интеграционные тесты не проверяют варианты склейки — они проверяют функционирование в сборе. Юнит-тесты проверяют работоспособность примитивов.
У тебя плодится много точек сборки, которые теперь приходится проверять. Ты решил терминов наизобретать? Что такое "работоспособность"? Чем она отличается от функционирования?

P>·>Я это знаю из тригонометрии, от моего кода не зависящей. А вот то, что fn2 — правильно, а fn3 — неправильно ты знаешь только из "как в коде написано". Т.е. твой код тестирует самого себя, порочный круг называется.

P>Не из кода, а из требований вызывающей стороны.
Что за требования вызывающей стороны, которые диктуют, что должен быть написан код с fn2, а не fn3? Вызывающая сторона это вообще что и где?

P>>>fn1 и fn2 должны быть видны в тестах, а не публично

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

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

P>·>И у вас в тестах может не оказаться тех самых значений для построения того самого ломающего запроса. Опять "тапочки не приносит"?
P>В моем случае таблица истинности рядом с тестом и функцией. Вы же ее пихаете куда подальше — прямо в бд.
P>Скажите честно — у вас есть требование по процессу во время код ревью обязательно смотреть содержимое тестовой бд? Те самые десятки-сотни-тысячи записей
Я не знаю что такое содержимое тестовой бд и накой оно вообще нужно. Я код теста уже показывал
Автор: ·
Дата: 28.12.23
, хватит фантазировать. Делается save и тут же find для только что сохранённых записей. Я уже писал, что тысячи записей не нужны в подавляющем большинстве случаев. А там где нужны — создаются программно в этом же тесте.

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

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

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

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

P>>>И вы уверены, что в тестовой бд всегда будет полный фарш? Вы что, один работаете?

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

P>>>Противоречие у вас в голове. Сказано — полного контроля не дают.

P>·>Именно. Зачем же тогда покрыть юнит-тестами?
P>-
P>Затем, что доказательство корректности — это необходимое условие. А такое доказательство + тесты + код ревью, необходимое и достаточное.
Если у тебя есть доказательство корректности кода, то тесты уже не нужны. Это и есть идея доказательного программирования.

P>·>И что мешает это же делать в repo+dbms?

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

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

P>Нет, не значит.
Ты сказал, что процесс билда и тестов занимает 2.5 часа. Это уже пол дня.

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

Я знаю, но это обычно недостаток, а не повод для гордости.

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

P>И так у вас всё — вместо вопросов занимаетесь телепатией.
Я задал вопрос — ты ответил 1.5ч + 40%, если надо, могу тебя процитировать, если ты запамятовал. Если ты умолчал важную инфу, релевантную для ответа на мой вопрос — это не моя вина.

P>>>Переписывать то зачем? Чем они вам мешают?

P>·>Тем, что они написаны на конкретном ЯП.
P>Я ж сказал — если по аннотации можно сгенерировать 4 артефакта, то почему нельзя сгенерировать 5й, 6й, 7й?
P>Вы же видите только переписывание
Вопрос в том, что является источником — прога на конкретном яп или language agnostic описание контракта.

P>>>·>Смотря как долго проект живёт.

P>>>Чем дольше, тем меньше шансов на переписывание, проще заново напилить новое.
P>·>Но при сохранении контракта, который у вас описан на аннотациях конкретного ЯП. В общем гугли ликбез, например, тут https://swagger.io/blog/code-first-vs-design-first-api/
P>Вы путаете code-first и design-first. design first — означает, что первое деливери это апи, а не реализация. code-first — наоборот. А какой файл вы в начале редактируете, json или java и yaml, дело десяток
Не десятое, а первое. Погляди в словаре что означает слово "first".

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

А начался разговор, что у тебя аннотации хитрые и сложные, зависят от переменных окружения и погоды на марсе.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[37]: Что такое Dependency Rejection
От: Pauel Беларусь http://blogs.rsdn.org/ikemefula
Дата: 02.01.24 16:03
Оценка: +1
Здравствуйте, ·, Вы писали:

P>>·>Недостаточно для чего? Почему недостаточно? Причём тут таблица истинности?

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

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

P>>Функционально — ровно столько же.

·>Нет. Он у меня ещё и содержит склейку. У тебя склейки нет.

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

P>>·>Меньше интеграционных за счёт того, что они тестируют интеграцию. А не всю копипасту, которую ты предлагаешь плодить.

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

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

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

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

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

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

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

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

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

·>Чем выше, тем больше мест. Функция nextFriday() осуществляет склейку у себя внутри. И покрывается одним тестом. У тебя будет nextFriday(now) — и на самом верху будет множество call site для этого накопипастено. Код линейный, да, но накопипастен в кучу мест.

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

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

·>В ю-тестах — по частям. Иначе изменение логики маппинга одного поля сломает все deep.equal тесты, где это поле есть.

Вы наверное не в курсе, то в джаве типизация хилая — не позволяет внятно типизировать согласованые значения, например "если ид = 0" то "created = null"
А раз так, то вы будете выписать такие согласованые значения руками.
Следовательно, и тестировать нужно ровно так же, а не размазывать значение по разным тестам
Будь у вас фукнциональный язык, можно было бы типизировать чуть не все подряд, но у вас такого фокуса нет.

P>>Пусть используется. Что вас смущает ?

·>Что в каждое из этих мест тебе понадобится копипастить склейку nextFriday(now) с источником текущего времени и покрывать это ещё одним тестом. Хотя, как я понял, ты вообще заявил, что такое протестировать ты просто не сможешь.

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

·>Ещё раз. nextFriday()nextFriday(now)timeSource.now(). Если ты отказываешься использовать в call site готовый и протестированный nextFriday(), значит тебе в каждый call site придётся протаскивать timeSource и копипастить композицию с nextFriday(now). Это увеличивает кол-во прод-кода и требует больше более тяжеловесных тестов для покрытия всей копипасты.


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

P>>Читайте себя "Так функция может использоваться из множества мест."

·>И?

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

P>>У вас в тех самых тестах все будет обмокано. Как узнаете, что там, в моках, должно вызываться не "то", а "это"?

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

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

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

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

P>>Наоборот — именно для таких мелочей юнит-тесты и нужны.
·>Ты не читаешь, что тебе пишут.

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

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

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

Еще раз — у меня нет буквально в коде того или иного запроса целиком, а есть конструирование его. Я ж прямо пишу — построение запроса по фильтрам.
Это значит, что у нас
1. связывание
2. маппер туда
3. маппер обратно
+ шаблоны запроса, шаблоны фильтров итд

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

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

·>Твой один интеграционный тест проверяет интеграцию одного do_logic. Таких do_logic у тебя будет дофига, и каждого надо будет проверить.

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

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

·>Интеграционный тест чего?

Чего угодно — хоть системы, хоть приложения, хоть компонента.

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

P>>Забавная телепатия.
·>Никакая не телепатия. Если ты никак не можешь протестировать, то проблемы будут обнаруживаться в проде. Ок, возможно, ещё при ручном тестировании.

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

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

P>>·>Это страшилки.
P>>Это следует из ваших примеров.
·>Не следует.

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

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

·>И что? Небо на землю упадёт?

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

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

·>Ну ввести константу — не проблема.

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

P>>·>Именно. Так что неясно зачем ты делаешь эти предъявы о гарантиях. У тебя аргументация вида "Раз ваш подход не умеет кофе варить и тапочки приносить — значит это плохо и надо делать как фаулер завещал".

P>>Вы забыли, что именно вы претендуете что решите это всё тестами.
·>Опять фантазии. Давай в следующий раз когда ты решишь что я думаю, на что я претендую, и т.п., подкрепляй моими цитатами.

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

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

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

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

P>>Интеграционные тесты не проверяют варианты склейки — они проверяют функционирование в сборе. Юнит-тесты проверяют работоспособность примитивов.

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

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

> Ты решил терминов наизобретать? Что такое "работоспособность"? Чем она отличается от функционирования?


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

P>>Не из кода, а из требований вызывающей стороны.

·>Что за требования вызывающей стороны, которые диктуют, что должен быть написан код с fn2, а не fn3? Вызывающая сторона это вообще что и где?

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

P>>Это ваша фантазия — эдак у вас юнит-тесты станут code smell, тк они принципиально по своей сути видят слишком много.

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

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

·>Я не знаю что такое содержимое тестовой бд и накой оно вообще нужно.


Во первых, врете, см ниже
Ну как же — вы собирались фильтры тестировать посредством выполнения запросов на бд, те самые "репозитории" которые вы тестируете косвенно, через БД. Или у вас фильтрация вне репозитория сделана? Ну-ну.

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

·>Верно. Я уже показал что лучше — сохранить данные в бд и поискать их разными фильтрами.

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

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

·>"я знаю, как строятся фильтры" — это и есть whitebox, не лукавь.

Как строятся — какой должен получиться в итоге запрос. А вот подробности того, какого цвета унутре неонки(связывание, конверсия, маппинг, итд) — мне не надо.
Чем сложнее логика построения запросов — тем больше тестов придется писать. Косвенные проверки не смогут побороть data complexity

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

·>Размазывание таблицы истинности придумал ты сам, сам и спорь со своими фантазиями.

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

P>>Затем, что доказательство корректности — это необходимое условие. А такое доказательство + тесты + код ревью, необходимое и достаточное.

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

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

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

P>>Нет, не значит.
·>Ты сказал, что процесс билда и тестов занимает 2.5 часа. Это уже пол дня.

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

·>Я знаю, но это обычно недостаток, а не повод для гордости.

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

P>>Я ж сказал — если по аннотации можно сгенерировать 4 артефакта, то почему нельзя сгенерировать 5й, 6й, 7й?

P>>Вы же видите только переписывание
·>Вопрос в том, что является источником — прога на конкретном яп или language agnostic описание контракта.

language agnostic описания, если вы про json, это отстой, т.к. удлинняет цикл разработки — есть шанс написать такой манифест, который хрен знает как заимплементить
Такое описание далеко не всегда нужно:
1. например, у нас всё на джаве — джава, скала, котлин, итд.
2. например, у нас нет внешних консумеров
3. например, мы связываем части своего собственного проекта — здесь вообще может быть один и тот же яп

P>>·>Но при сохранении контракта, который у вас описан на аннотациях конкретного ЯП. В общем гугли ликбез, например, тут https://swagger.io/blog/code-first-vs-design-first-api/

P>>Вы путаете code-first и design-first. design first — означает, что первое деливери это апи, а не реализация. code-first — наоборот. А какой файл вы в начале редактируете, json или java и yaml, дело десяток
·>Не десятое, а первое. Погляди в словаре что означает слово "first".

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

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

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

Основа апи остаётся. Переменные окружения и конфиги не могут поменять параметры метода, типы, результат итд. Но могут скрыть этот метод, или добавить метаданные, например, метод начнет требовать авторизацию, или наоборот, будет работать без авторизации.
Re[38]: Что такое Dependency Rejection
От: · Великобритания  
Дата: 03.01.24 00:22
Оценка:
Здравствуйте, Pauel, Вы писали:

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

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

P>>>Функционально — ровно столько же.

P>·>Нет. Он у меня ещё и содержит склейку. У тебя склейки нет.
P>Нету такой функции как склейка. Вы все еще думаете вайтбоксом.
Частичное применение функции — даёт функцию.

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

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

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

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

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

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

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

P>·>Чем выше, тем больше мест. Функция nextFriday() осуществляет склейку у себя внутри. И покрывается одним тестом. У тебя будет nextFriday(now) — и на самом верху будет множество call site для этого накопипастено. Код линейный, да, но накопипастен в кучу мест.
P>Нужно проверять все эти call site вне зависимости от способа доставки депенденсов, каждая функция должна быть вызвана в тестах минимум один раз.
Проверять на что и каким тестом? В call site я могу использовать мок nextFriday() и проверять поведение ю-тестами.

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

P>·>В ю-тестах — по частям. Иначе изменение логики маппинга одного поля сломает все deep.equal тесты, где это поле есть.
P>Вы наверное не в курсе, то в джаве типизация хилая — не позволяет внятно типизировать согласованые значения, например "если ид = 0" то "created = null"
P>А раз так, то вы будете выписать такие согласованые значения руками.
P>Следовательно, и тестировать нужно ровно так же, а не размазывать значение по разным тестам
P>Будь у вас фукнциональный язык, можно было бы типизировать чуть не все подряд, но у вас такого фокуса нет.
Не очень понял. Наверное мы говорим о разных вещах. Покажи код где там какой типизации не хватает в java.

P>>>Пусть используется. Что вас смущает ?

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

P>·>Ещё раз. nextFriday()nextFriday(now)timeSource.now(). Если ты отказываешься использовать в call site готовый и протестированный nextFriday(), значит тебе в каждый call site придётся протаскивать timeSource и копипастить композицию с nextFriday(now). Это увеличивает кол-во прод-кода и требует больше более тяжеловесных тестов для покрытия всей копипасты.

P>Вам точно так же нужно протаскивать timeSource, только без параметра. И тестировать нужно ровно столько же, т.к. нужно знать, как ведет себя вся интеграция разом.
Не нужно его протаскивать. Он уже "частично применён" в CalendarLogic. Ты вообще понимаешь что такое ЧПФ?!

P>>>Читайте себя "Так функция может использоваться из множества мест."

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

P>·>Эээ.. Ты рефакторингом точно умеешь пользоваться? Компилятор и IDE всё покажет, подскажет и подправит.

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

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

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

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

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

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

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

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

P>>>А если таблица истинности рядом с тестами и тестируемой функцией, шансов намного больше что проблему заметят еще во время код ревью.
P>·>Нет, не пытаюсь, ты опять фантазируешь. Я пытаюсь донести мысль, что надо проверять что код делает, а не как он выглядит.
P>Еще раз — у меня нет буквально в коде того или иного запроса целиком, а есть конструирование его.
Цитирую тебя:

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

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

P>Я ж прямо пишу — построение запроса по фильтрам.

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

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

P>·>Твой один интеграционный тест проверяет интеграцию одного do_logic. Таких do_logic у тебя будет дофига, и каждого надо будет проверить.
P>Этот do_logic по одному на юз кейс. Сколько юз кейсов — столько и таких методов, а значит столько будет и интеграционных тестов. И у вас будет ровно то же. И никаие моки этого не отменят.
P>Даже если вы вообще удалите зависимость от Time.now, это ничего не изменит — количество интеграционных тестов останется прежним
А у меня этих твоих do_logic — по нулю на юз-кейс. Чуешь разницу?

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

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

P>·>Не следует.

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

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

P>У вас выходят вайтбокс тесты без недостатков свойственных вайтбокс тестам.

P>Чудо, которое можно объяснить только вашими фантазиями
Допускаю, что я неправильно терминологию использую white/black box...

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

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

P>·>Ну ввести константу — не проблема.

P>Её нужно вводить, хотя бы для того, что бы обеспечить согласованность некоторых тестов.
P>А все лишние из тестов нужно убрать, что бы ничего не мешало делать ревью
Это всё мелочи жизни, применимо к любому коду. В любом коде хорошо вводить константы, если они означают одно и то же в разных частях кода. Любой код надо делать так, чтобы было проще читать/ревьювить. При чём тут сабж?

P>Чем больше, тем лучше. Если моки можно убрать — их нужно убрать

Это уже твои личные заморочки от неумения пользоваться моками.

P>>>Вы забыли, что именно вы претендуете что решите это всё тестами.

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

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

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

P>>>Интеграционные тесты не проверяют варианты склейки — они проверяют функционирование в сборе. Юнит-тесты проверяют работоспособность примитивов.

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

>> Ты решил терминов наизобретать? Что такое "работоспособность"? Чем она отличается от функционирования?

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

P>>>Не из кода, а из требований вызывающей стороны.

P>·>Что за требования вызывающей стороны, которые диктуют, что должен быть написан код с fn2, а не fn3? Вызывающая сторона это вообще что и где?
P>Например, те самые требования. Если нам вернуть нужно User, то очевидно, что fn2 должна возвращать User, а не абы что.
Ерунда какая-то.
Во-первых, это очень слабая проверка, т.к. такое работает если только у тебя _каждая_ функция имеет уникальный тип результата.
Во-вторых, вспомним код:
const getUser = repository.getUser(id);
const oldUser = db.execute(getUser);
const newUser = bl.modifyUser(oldUser); // вот тут у тебя должен быть compilation error, если getUser будет возвращать что-то отличное от User.

Ты своими тестами компилятор тестируешь что-ли? Или у тебя настолько "типизация нехилая", что каждый тип приходится тестом покрывать?!

P>>>Это ваша фантазия — эдак у вас юнит-тесты станут code smell, тк они принципиально по своей сути видят слишком много.

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

P>·>Я не знаю что такое содержимое тестовой бд и накой оно вообще нужно.

P>Во первых, врете, см ниже
Мимо.

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

Да.

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

Нет.

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

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

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

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

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

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

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

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

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

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

P>>>Затем, что доказательство корректности — это необходимое условие. А такое доказательство + тесты + код ревью, необходимое и достаточное.

P>·>Если у тебя есть доказательство корректности кода, то тесты уже не нужны. Это и есть идея доказательного программирования.
P>Ну и дичь Каким чудом ваше доказательство сработает, если в код влезет другой ?
P>Дональд Кнут на линии: "Остерегайтесь ошибок коде; я только доказал его правильность, но не проверял его."
Ты либо крестик, либо трусы. Док.программирование это спеки, контракты и верификаторы. Тесты тут непричём.

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

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

P>·>Вопрос в том, что является источником — прога на конкретном яп или language agnostic описание контракта.

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

P>Такое описание далеко не всегда нужно:

P>1. например, у нас всё на джаве — джава, скала, котлин, итд.
P>2. например, у нас нет внешних консумеров
P>3. например, мы связываем части своего собственного проекта — здесь вообще может быть один и тот же яп
Тогда нафиг и не нужны аннотации, и уж тем более манифесты.

P>·>Не десятое, а первое. Погляди в словаре что означает слово "first".

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

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

P>·>А начался разговор, что у тебя аннотации хитрые и сложные, зависят от переменных окружения и погоды на марсе.
P>Основа апи остаётся. Переменные окружения и конфиги не могут поменять параметры метода, типы, результат итд. Но могут скрыть этот метод, или добавить метаданные, например, метод начнет требовать авторизацию, или наоборот, будет работать без авторизации.
И на чём эта вся логика написана? На grpc, да?
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Отредактировано 03.01.2024 10:18 · . Предыдущая версия . Еще …
Отредактировано 03.01.2024 0:31 · . Предыдущая версия .
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 возможно не всё умеет, я не сильно в ём понимаю, но принципиальных проблем не вижу.
Re[40]: Что такое Dependency Rejection
От: · Великобритания  
Дата: 06.01.24 21:37
Оценка:
Здравствуйте, Pauel, Вы писали:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  Return serialize(p)


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Мрак.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

P>Вы похоже не вдупляте — design-first = вначале деливерим АПИ, а потом уже реализация, куда входят переменные, конфиги, логика тенантов итд.
http-сервер это такой АПИ... Угу-угу.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Отредактировано 06.01.2024 21:49 · . Предыдущая версия .
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 тащите. Дурака валяете.
Re[42]: Что такое Dependency Rejection
От: · Великобритания  
Дата: 11.01.24 22:32
Оценка:
Здравствуйте, Pauel, Вы писали:

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

P>·>Вопрос я не понял, мол что делать если что-то меняется, ответил как мог — если что-то меняется, то эээ... надо что-то менять. Если тебя интересует конкретная проблема, сформулируй с примерами кода.
P>Вы мок затачивали на все тесты, или по моку на каждый?
P>1. На все — все ваши тесты неявно теперь зависят от мока. И если вы закостылили мок, то заточили тесты под синтетику, код — под такие тесты.
P>2. Если по моку на каждый — можно хоть ревью сделать. Кода больше, но и контроля больше.
Я не понимаю что такое заточка мока. Код с моками это просто код. Там где код общий, там он переиспользуется из разных мест. Там где не общий, там не переиспользуется. Мок это просто механизм передачи данных. Ровно такой же вопрос я могу спросить про твои параметры.

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

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

P>И ваши моки не избавляют этого.

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

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

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

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

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

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

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

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

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

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

P>Связывание и есть wiring код. Основная обязанность контроллера — именно это. И тестировать связку, как следствие.
Нет, обязанность контрллера связывать входящие запросы с бизнес-логикой. wiring code — это отдельный код для связывания компонент друг с другом.

P>Если же вы сторонник жирных контроллеров, конечно же вам придется мокать всё на свете. Не только time, а еще и sin и cos

Это твои фантазии, спорить не буду.

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

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

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

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

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

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

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

P>·>Поменяется InstantSource, будет добывать время не из system clock, а из таймстампа в очереди. Это вообще никакое не архитектурное изменение, а одна строчка в wiring code — что инжектить как источник времени.
P>О — InstantSource появился. Значит инжектиться может не один единственный вариант с Now, а минимум два.
А ты вообще код мой читал? В первой строчке код-сниппета
Автор: ·
Дата: 28.12.23
. Ты по-моему сам со своими фантазиями споришь, я тебе только мешаю.

P>Где у вас тестирование инжекции? А ну как на проде все инжектиться будет по старому?

В прод-classpath не видны моки и вообще вся тестовая белиберда. Код не скомпилится просто, если туда правильную зависимость не вписать.

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

P>·>Что за отложенный вызов? Вызов где?
P>А как вы понимаете, что такое отложеный вызов? Недавно вам непонятно было что такое dependency injection, потом выяснилось, что все вам известно.
Мне не было понятно, что ты под этим понимаешь.

P>Тут похоже вам снова надо с самим собой поговорить

Ну ссылку на вики дай.

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

P>·>Ну будет два InstantSource в разных местах.
P>Вот-вот. Как вы отличите что первый и второй не перепутаны? Это ж ваша любимая тема — надо проверить, что передали то самое!
Устанавливаем в тестах, что "у нас на сервере now это 21:00:01, а на клиенте "21:00:02". И в тестах можно ассертить что в нужных случаях ожидаемое время. Что ты будешь делать со своим синлтоном Time.Now — ты так и не рассказал. Вообще я не понимаю как ты такое можешь использовать и тем более тестировать, просвети.
Что синглтоны — плохо учат ещё в детском садике, ясельной группе.

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

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

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

P>·>Это будет две функции. ЧПФ, слышал?
P>Я в курсе: "ассертим что вызываем с тем параметром а не этим"
Бред.

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

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

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

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

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

P>·>Круто, но зачем? Задачу-то какую решаем?
P>Затем, что признак хорошего кода это его гибкость, которая проявляется в т.ч. в том, что код можно трансформировать, инлайнить, итд, да хоть вовсе от него избавиться.
Если при этом нужно плодить копипасту, то это явно что-то не то. И неясно причём тут инлайн и гибкость кода. str.length() — это негибкий код?!

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

P>Вы так и не рассказали, как будете решать ту самую задачу с фильтрами.
Рассказал и даже код показал,
Автор: ·
Дата: 28.12.23
насколько понял твою задачу. Если ещё остались вопросы, спрашивай.

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

P>·>Дело в том, что в случае субд у тебя кода субд нет.
P>И не надо.
Надо, если хотим тестировать поведение, а не детали имплементации.

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

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

P> Кое что, для аналитики — возможно. Но это большей частью всё равно перегоняется в орм или билдер. Есть орм — можно проверять построение логики запросов орм.

В смысле ты в своих тестах linq/etc тестируешь? Зачем? У linq свои тесты есть.

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

P>·>Ты вроде сам заявлял, что тестировать нужно ожидания. Какое отношение метапрограммирование имеет к бизнес-требованиям или ожиданиям?
P>Запрос к базе должен соответсвовать бизнес-требованиям. Только проверяем мы это в т.ч. посредством метапрограммирования.
Что за бред? У тебя в FRD прописаны тексты запросов??! Прям где-то у вас есть бизнес-требование "select * from users where id=?" и "in: [fn1]"?! Приходит к вам клиент и говорит "а мне очень нужно чтобы out: [fn2], когда можете сделать?"

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

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

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

Я код
Автор: ·
Дата: 28.12.23
привёл. Что не устраивает-то??! "ищем" — это "find", английский язык знаешь?

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

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

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

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

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

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

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

P>·>Куда смотреть? Поломал что?
P>Вам надо мок подфиксить, что бы время было той системы
Чё?

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

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

P>и у вас кейс с вырожденным фильром до сих пор не покрыт тестом.

Я не видел этого кейса, и не могу нафантазировать какие у тебя возникли проблемы его покрыть.

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

P>·>Я тебе приводил пример — заменить звёздочку на список полей — это чих, а не изменение бизнес логики.
P>Не знаю, что у вас с этим за проблема, тут рукописные логи годами ходят в одном и том же виде, тригерят эвенты, и ничего. А гарантировать сохранность стиля генерируемого кода почему то вам кажется рокетсаенсом.
Да причём тут стиль? Это мелкий чих. Я могу переписать например "SELECT ... X OR Y" на "UNION SELECT ... X ... SELECT ... Y" для оптитмизации. У тебя все тесты посыпятся и любая ошибка — увидишь после деплоя и прогона многочасовых тестов, в лучшем случае (а скорее всего только в проде). А у меня только те тесты грохнутся, если я реально ошибусь и что-то поломаю в поведении, ещё до коммита.

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

P>·>Если сборка идёт в трёх точках — проверить легко. Если сборка идёт в трёхсот точках — проверить сложно.
P>Вы почемуто забываете про dependency injection — это такой код, который труднее всего покрыть тестами, а между тем у него чудовищное влияние на систему.
P>Потому и стоит менять дизайн что бы это контролировать, а через di протаскивать только технологические зависимости.
Зато этот код очень простой, линейный и на 99% покрывается компилятором. Остальной 1% покрывается стартом приложения.

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

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

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

P>·>Как ты _проверишь_, что результат тот же и соответствует ожиданиям?
P>Очень просто — у нас уже есть тесты, одни должны сохраниться без каких либо изменений.
Я спросил как сделать так, чтобы такие тесты были, ты ответил
Автор: Pauel
Дата: 31.12.23
"Никак.". Т.е. тестов у вас таких просто нет.

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

P>Теперь изменение реализации должно сохранить тесты зелеными
Эээ.. ну да... верно. После любого такого изменения ни один тест не покраснеет. no matter what, т.к. с тестами это тестирующих у вас "никак".

P>·>

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

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

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

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

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

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

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

P>·>Мрак.
P>Видите — не так, как вы привыкли и сразу ужос-ужос-мрак-и-смэрть
Ну вроде это уже вс проходили и забили. Заталкивать тесты в прод-код... зачем??!

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

P>·>А конкретнее? С примером кода.
Именно. Примера кода я так и не дождусь.

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

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

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

P>·>Зачем нам тестировать выхлоп билдера? У билдера должны быть свои тесты.
P>Это и есть тесты билдера. Только конкретного, в котором заложено много различных вырожденных кейсов. linq не в курсе, что вы вытаскиваете всю базу, ему по барабану. Мне — нет.
Linq выдаёт список результатов — вот и проверяй, что в этом списке — вся база или не вся.

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

P>·>Не понимаю проблему. Если фильтр выродится в true, то субд вернёт те записи, которые мы не ожидаем, тест провалится.
P>Ну так найдите эту комбинацию. А их, мягко говоря, немало. Как будете искать?
Анализом требовний, кода, данных, етс. Обычный corner case analysis. А как вы будете искать?

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

P>·>Верификатор выдаст ошибку. Почитай про какой-нибудь coq что-ли.
P>И вы так прямо и втащите coq в любом проект на джаве?
Нет. А вы таки втащили доказательное программированипе в js?

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

P>Еще раз — фильтр не хардкодится, а строится.
Я потерял суть. О чём речь вообще? Пример в студи. КОД!

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

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

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

Ну да. Откуда же берутся конфиги/тенанты/http-запросы?

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

Расскажи где в graphql/grpc можно найти конфиги/аннотации/переменные окружения?

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

P>·>Ты опять врёшь. Не телепатией, а из твоих слов: "Манифест генерируется по запросу, обычно http", противоречие выделил жирным. Или ты похоже просто в прыжке переобуваешься.
P>Вы вместо уточнения пытаетесь в телепатию играть. Если у нас готовое приложение, то конечно же генерируется — и странно было бы иначе делать.
Угу. ЧТД. "готовое приложение" == "code-first".

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

Откуда в апи у тебя возьмутся тенаты? Или ты просто словоблудием занимаешься? Когда удобно имеешь в виду gprc, а когда удобно вкидываешь про готовое приложение — лишь бы собеседника переспорить.

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

P>·>http-сервер это такой АПИ... Угу-угу.
P>Я вам про интерфейсы, а вы сюда http-server тащите. Дурака валяете.
Не ври. http втащил ты. Цитиую: "по запросу, обычно http".
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Отредактировано 11.01.2024 23:36 · . Предыдущая версия . Еще …
Отредактировано 11.01.2024 23:30 · . Предыдущая версия .
Отредактировано 11.01.2024 23:23 · . Предыдущая версия .
Re[43]: Что такое Dependency Rejection
От: Pauel Беларусь http://blogs.rsdn.org/ikemefula
Дата: 13.01.24 18:52
Оценка:
Здравствуйте, ·, Вы писали:

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

P>>2. Если по моку на каждый — можно хоть ревью сделать. Кода больше, но и контроля больше.
·>Я не понимаю что такое заточка мока. Код с моками это просто код. Там где код общий, там он переиспользуется из разных мест. Там где не общий, там не переиспользуется. Мок это просто механизм передачи данных. Ровно такой же вопрос я могу спросить про твои параметры.

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

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

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

То-то и оно. Вот вы снова сами себе и ответили

P>>И ваши моки не избавляют этого.

·>Моки не избавляют. А помогают избавляться.

Нисколько. Моки прибивают вас к конкретному дизайну — см пример про инлайн кода. Этот пример демонстрирует особенность — даже ИДЕ перестает помогать.
А вот если вы отказываетесь от моков, соотвественно, ИДЕ вам в помощник — вдруг рефакторинг inline начинает работать сам собой

P>>Вариантов дизайна всегда больше одного

·>Именно. Это всё субъективщина. Эти варианты у тебя меняются каждые год два, как женские шляпки. А вот требование ресурсов, использование сети-тредов-диска — это объективные критерии. На них я и ориентируюсь, а не на последние веяния моды.

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

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

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

Я ж объяснил — таблица истинности рядом с тестом и кодом, моки-стабы тоньше некуда. Вы рассматриваете систему в отрыве от тестов. Это категрически неверно. Даже конкретный деплоймент не имеет смысла без тестов — чемодан без ручки. Нужно проверить, а все ли путём, без тестов это никак. Более того — поменялись внешние зависимости — снова нужны тесты.
Соответсвенно нужно выбирать такое решение, которое проще мейнтейнить, с учетом тестов.

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

·>Нет.

У вас похоже особое видение каждого термина, который мы обсуждаем. ТДД по Кенту Беку целиком про дизайн.
Вот код теста — как только вы его написали, решение о том, как будут протягиваться зависимости, уже принято
const calendar = new CalendarLogic(Predefined.Now);

expect(nextFriday()).to.eq(Predefined.Friday);


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

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

@test()
@params(
    1,2,3,4,5,6  // и так на каждую строчку в табличке
)
@params(
    8,4,2,1,3,5 // точно так же и исключения прокидывают
)
...   сколько строчек, столько и тестов фактически выполнится
"a cool name test name" (v1, v2, v3, repo1, svc, repo2, result) {
   let logic = Logic(v1,v2,v3);

   let r = logic.run({
      load: () => repo1,
      load2: () => svc,
      ... и так прокидываем все что надо
   }) 

   expect(r).to.deep.eq(result)
}


P>>Связывание и есть wiring код. Основная обязанность контроллера — именно это. И тестировать связку, как следствие.

·>Нет, обязанность контрллера связывать входящие запросы с бизнес-логикой. wiring code — это отдельный код для связывания компонент друг с другом.u

Вы сейчас в слова играете. Сами же пишете — "обязанность контроллера связывать" Только еще вводите понятие wiring code.
Часть кода связывания будет вне контролера, и правила вы водите сами, это и есть тот дизайн. Прокидываете через dependency injection — значит внутри контролера этого не будет.
Прокидываете now в контролере — эту часть логично исключить из dependency injection.

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

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

P>>Сами спросили — сами ответили.
·>Откуда _у тебя_ null берётся? У меня никаких null тут нет.

У вас CalendarLogic принимает зависимость к конструкторе. Технически, эта зависимость может быть и null, и предзаписаной, и привязаной к контексту — не принципиально. Ваша задача — сделать так, что бы в контролер этот CalendarLogic пришел с нужной завимостью, а не произвольной. Соответсвенно, гарантии фиксируем через интеграционный тест — конкретный use case собран как положено. И вот здесь внутренности реализации вообще роли не играют — проверяется интеграция, а не внутреннее устройство.

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

·>А ты вообще код мой читал? В первой строчке код-сниппета
Автор: ·
Дата: 28.12.23
. Ты по-моему сам со своими фантазиями споришь, я тебе только мешаю.


Вы похоже сами свой код не читали, т.к. у вас недоумение, откуда может быть null. И ежу понятно — будет передан в конструктор или явно, или неявно. Правильно это или нет — скажут тесты.

P>>Где у вас тестирование инжекции? А ну как на проде все инжектиться будет по старому?

·>В прод-classpath не видны моки и вообще вся тестовая белиберда. Код не скомпилится просто, если туда правильную зависимость не вписать.

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

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

·>Мне не было понятно, что ты под этим понимаешь.

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

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

·>Устанавливаем в тестах, что "у нас на сервере now это 21:00:01, а на клиенте "21:00:02". И в тестах можно ассертить что в нужных случаях ожидаемое время. Что ты будешь делать со своим синлтоном Time.Now — ты так и не рассказал. Вообще я не понимаю как ты такое можешь использовать и тем более тестировать, просвети.

Просто разные аргументы.

·>Что синглтоны — плохо учат ещё в детском садике, ясельной группе.


Вы наверное не поняли — никаких синглтонов, только параметры.

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

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

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

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

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

Интеграционным, которых ровно столько же, сколько и у вас.

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

·>Если при этом нужно плодить копипасту, то это явно что-то не то. И неясно причём тут инлайн и гибкость кода. str.length() — это негибкий код?!

Вы сейчас перевираете. Речь про инлайн вашего nextFriday(). Об ваш супер-крутой подход почему то ИДЕ спотыкается и вы проблемы в этом не видите.

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

·>Рассказал и даже код показал,
Автор: ·
Дата: 28.12.23
насколько понял твою задачу. Если ещё остались вопросы, спрашивай.


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

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

P>>И не надо.
·>Надо, если хотим тестировать поведение, а не детали имплементации.

Тестируйте базу, я ж не мешаю. Можете еще и операционку и джава рантайм сюда добавить

P>>Билдера запросов к базе данных. Вы что думаете, все sql запросы руками пишутся?

·>Да похрен как они пишутся, хоть левой пяткой. Это всё детали реализации. Главное что они работают ожидаемым способом.

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

P>> Кое что, для аналитики — возможно. Но это большей частью всё равно перегоняется в орм или билдер. Есть орм — можно проверять построение логики запросов орм.

·>В смысле ты в своих тестах linq/etc тестируешь? Зачем? У linq свои тесты есть.

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

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

·>Что за бред? У тебя в FRD прописаны тексты запросов??! Прям где-то у вас есть бизнес-требование "select * from users where id=?" и "in: [fn1]"?! Приходит к вам клиент и говорит "а мне очень нужно чтобы out: [fn2], когда можете сделать?"

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

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

·>Цитаты нет. Т.е. соврал.

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

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

·>Я код
Автор: ·
Дата: 28.12.23
привёл. Что не устраивает-то??! "ищем" — это "find", английский язык знаешь?


Смотрите сами: assertThat(testSubject.find(new Filter.byEqual("Vasya"))).isPresent();
У вас testSubject это или репозиторий, который вы предлагаете с подключеной бд тестировать, или компонент, который дергает мок репозитория
Ни то, ни другое проблему с фильтрами не найдут.

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

P>>·>Куда смотреть? Поломал что?
P>>Вам надо мок подфиксить, что бы время было той системы
·>Чё?

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

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

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

·>Покажи пример бизнес-требования.

зачем?

P>>и у вас кейс с вырожденным фильром до сих пор не покрыт тестом.

·>Я не видел этого кейса, и не могу нафантазировать какие у тебя возникли проблемы его покрыть.

А он и не виден, т.к. проблема тупо в количестве вариантов которые могут порождать фильтры. Та самая data complexity.

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

·>Да причём тут стиль? Это мелкий чих. Я могу переписать например "SELECT ... X OR Y" на "UNION SELECT ... X ... SELECT ... Y" для оптитмизации. У тебя все тесты посыпятся и любая ошибка — увидишь после деплоя и прогона многочасовых тестов, в лучшем случае (а скорее всего только в проде). А у меня только те тесты грохнутся, если я реально ошибусь и что-то поломаю в поведении, ещё до коммита.

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

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

·>Зато этот код очень простой, линейный и на 99% покрывается компилятором.

Не проверяется. Ваш CalendarLogic может принимать разные экземпляры — с текущим временем, клиентским, серверным, из параметра, итд. Всё что вам компилятор скажет, что типы нужные.
Ну и что?
Кто даст гарантию, что в проде вы правильно прописали зависимости для CalendarLogic? Интерфейс то один и то же

> Остальной 1% покрывается стартом приложения.


Еще меньшая гарантия.

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

·>Я спросил как сделать так, чтобы такие тесты были, ты ответил
Автор: Pauel
Дата: 31.12.23
"Никак.". Т.е. тестов у вас таких просто нет.


Вы вопрос свой прочитать в состоянии? как именно ты собираешься в "curl" и-тестах ассертить
Буквально curl тест все что может, это сравнить выхлоп, и всё. Если там время не фигурирует, то никак.
Если же вы имеете ввиду интеграционные, то будет все ровно как и у вас.

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

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

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

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

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

См выше — все нефунциональные вещи нужно проверять и фиксировать тестами.

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

·>Ну вроде это уже вс проходили и забили. Заталкивать тесты в прод-код... зачем??!

Затем, что это быстрый, дешовый и надежный способ узнавать о проблемах с приложением в целом
1. поменялись зависимости на той стороне — убедились, что все в порядке, или сообщили о проблеме, вкатили хотфикс
2. поменялась инфраструктура — убедились, что все в порядке
3. протух-обновился сертификат — оно же
4. обновили часть системы — оно же, тестируем всю
5. высокая нагрузка — снимаем наши собственные значения, получаем ту инфу которую другим способом никак не получить. Например — некоторые юзеры под высокой нагрузкой получают некорретные данные.

Покажите, как вы все эти кейсы обнаруживать будете. Полагаю ответ будет "у нас багов на проде нету" ?


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

P>>·>А конкретнее? С примером кода.
·>Именно. Примера кода я так и не дождусь.

насколько я понимаю, это я все еще жду вашего примера про фильтры, который решает ту самую data complexity

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

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

Вход на фильтры полным перебором подавать или магией воздействовать?

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

·>Анализом требовний, кода, данных, етс. Обычный corner case analysis. А как вы будете искать?

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

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

·>Нет. А вы таки втащили доказательное программированипе в js?

Зачем? Доказывать свойства можно и без coq, только результаты нужно выражать в дизайне и подкреплять тестами.

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

·>Я потерял суть. О чём речь вообще? Пример в студи. КОД!

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

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

·>Верно. В design-first деливерится манифест first.

Не просто манифест — а любое описание, абы ваши пиры могли его понять и цикл разработки был быстрым.

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

·>Ну да. Откуда же берутся конфиги/тенанты/http-запросы?

Появятся после того, как вы заделивирите АПИ, например спустя год, когда у вас уже версия 1.x. И вот здесь вы решите перейти на rust, то в этот момент крайне странно топить за design-first — у нас уже готовое приложение версии 1.x, и нужно сделать так, что бы манифест отрендерился — с учетом тенантов и прочих вещей, включая not implemented. Или вы собираетесь бегать по отделам и рассказывать "а чо вы это делаете — мы же на джаве год назад это решили не делать" ?

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

P>>Вы вместо уточнения пытаетесь в телепатию играть. Если у нас готовое приложение, то конечно же генерируется — и странно было бы иначе делать.
·>Угу. ЧТД. "готовое приложение" == "code-first".

Я вам говорю откуда возьмется апи в готовом, если вы решите уже готовое переписать на другом языке.
Re[44]: Что такое Dependency Rejection
От: · Великобритания  
Дата: 14.01.24 18:56
Оценка:
Здравствуйте, Pauel, Вы писали:

P>·>Я не понимаю что такое заточка мока. Код с моками это просто код. Там где код общий, там он переиспользуется из разных мест. Там где не общий, там не переиспользуется. Мок это просто механизм передачи данных. Ровно такой же вопрос я могу спросить про твои параметры.

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

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

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

P>>>И ваши моки не избавляют этого.

P>·>Моки не избавляют. А помогают избавляться.
P>Нисколько. Моки прибивают вас к конкретному дизайну — см пример про инлайн кода.
Вас может прибивают, нас — не прибивают.

P>Этот пример демонстрирует особенность — даже ИДЕ перестает помогать.

Помогать в чём? Ещё раз — инлайнинг и т.п. — это средство, а не цель. С какой целью что инлайнить-то?

P>А вот если вы отказываетесь от моков, соотвественно, ИДЕ вам в помощник — вдруг рефакторинг inline начинает работать сам собой

Ок, расскажи от каких моков надо избавиться, чтобы рефакторинг inline для str.length() заработал?

P>>>Вариантов дизайна всегда больше одного

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

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

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

P>Соответсвенно нужно выбирать такое решение, которое проще мейнтейнить, с учетом тестов.

Угу. И моки тут помогают.

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

P>·>Нет.
P>У вас похоже особое видение каждого термина, который мы обсуждаем. ТДД по Кенту Беку целиком про дизайн.
P>Вот код теста — как только вы его написали, решение о том, как будут протягиваться зависимости, уже принято
ТДД диктует необходимость того, чтобы код был тестируемый, а не про то куда как тянуть зависимости. Единственно что он может диктовать, что зависимостями надо управлять явно, чтобы можно было прокидывать тестовые.

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

P>>>Какой еще "правильный"? Я продемонстрировал, что минимальными усилиями можно свести тесты к таблице истинности и хранить все это максимально близко к коду.
P>·>Я видимо не понял. Где какие таблицы?
P> 1,2,3,4,5,6 // и так на каждую строчку в табличке
Не понимаю. Ты, похоже, стиль кода путаешь с дизайном. А дизайн с управлением зависимостями. Ну сделай себе таблицу истинности и с моками, и с параметрами:
void truthTable(int x, int y, int z) {
   when(dep.getX()).thenReturn(x);
   assert testSubject.doSomething(y) == z;
}

@Test withTableAsYouWish() {
   truthTable(1, 2, 5);
   truthTable(10, 3, 42);
}

Можешь и аннотации забабахать как у тебя выше. Или цикл. Или на разные тесты (чтобы имена давать осмысленные).
@params(
    1,2,3,4,5,6  // и так на каждую строчку в табличке
)
@params(
    8,4,2,1,3,5 // точно так же и исключения прокидывают
)
@Test withTableAsYouWish(x, y, z) {
   when(dep.getX()).thenReturn(x);
   assert testSubject.doSomething(y) == z;
}

Аннотации мне не нравятся, т.к., например, отладчиком сложно пройтись.

P>>>Связывание и есть wiring код. Основная обязанность контроллера — именно это. И тестировать связку, как следствие.

P>·>Нет, обязанность контрллера связывать входящие запросы с бизнес-логикой. wiring code — это отдельный код для связывания компонент друг с другом.u
P>Вы сейчас в слова играете. Сами же пишете — "обязанность контроллера связывать"
Ну не просто "связывать", а _что_ связывать. Шнурки связывать, контроллер, например, не умеет, хоть с моками, хоть без.
Связывание компонент это не то же, что связывание запросов с логикой.

P> Только еще вводите понятие wiring code.

Ок. Возможно я использовал неизвестную тебе терминологию. Это из мира управления зависимостей. "wiring up dependencies", "autowiring", под я под wiring code здесь имел в виду "composition root".

P>Часть кода связывания будет вне контролера, и правила вы водите сами, это и есть тот дизайн. Прокидываете через dependency injection — значит внутри контролера этого не будет.

Да похрен на дизайн. Это лишь меняет место где "волшебство" происходить будет.

P>Прокидываете now в контролере — эту часть логично исключить из dependency injection.

В контроллере now откуда возьмётся?

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

Не понял, чем легче?

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

P>>>Сами спросили — сами ответили.
P>·>Откуда _у тебя_ null берётся? У меня никаких null тут нет.
P>У вас CalendarLogic принимает зависимость к конструкторе. Технически, эта зависимость может быть и null, и предзаписаной, и привязаной к контексту — не принципиально.
Это тут причём? Технически передавая через параметр твой now — тоже может быть null и т.п. Всё то же самое.
Но есть очень важная разница в том, что конструктор таких зависимостей (по крайней мере подавляющего большинства зависимостей) вызывается в момент старта приложения, в composition root. И банальная requireNotNull (если яп не поддерживает not-null типы на этапе компиляции) в худшем случае будет железно ронять деплоймент, если конфиги плохие.
В твоём же случае впихивание null будет происходить только при вызове функции приложения. Поэтому вам и требуются тесты всего уже после "успешгого" деплоя в проде.

P>Ваша задача — сделать так, что бы в контролер этот CalendarLogic пришел с нужной завимостью, а не произвольной. Соответсвенно, гарантии фиксируем через интеграционный тест — конкретный use case собран как положено. И вот здесь внутренности реализации вообще роли не играют — проверяется интеграция, а не внутреннее устройство.

А как ты будешь такой и-тест писать, если у тебя будет Time.Now? Что ассертить-то будешь? Как отличать два разных вызова Time.Now — клиентский от серверного? Впрочем, ты уже ответил "никак". Или я что-то пропустил?

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

P>·>А ты вообще код мой читал? В первой строчке код-сниппета
Автор: ·
Дата: 28.12.23
. Ты по-моему сам со своими фантазиями споришь, я тебе только мешаю.

P>Вы похоже сами свой код не читали, т.к. у вас недоумение, откуда может быть null. И ежу понятно — будет передан в конструктор или явно, или неявно. Правильно это или нет — скажут тесты.
Так же можно явно или неявно передать now=null в твой nextFriday(now). Ты опять вместо того, чтобы сравнивать два подхода начинаешь общие слова говорить.

P>>>Где у вас тестирование инжекции? А ну как на проде все инжектиться будет по старому?

P>·>В прод-classpath не видны моки и вообще вся тестовая белиберда. Код не скомпилится просто, если туда правильную зависимость не вписать.
P>Успокойтесь — у вас есть провайдер time, вам нужно больше одной реализации — время может тащиться из разных источников. И ваш CalendarLogic работает с любой из таких зависимостей.
P>А щас вы расказываете басню, что компилер как то отличит один провайдер времени от другого времени с таким же интерфейсом
Компилятор может отличать имена символов. Это же в коде видно и на ревью всё очень очевидно — где какое поле куда передаётся. Впрочем, если у тебя всё равно паранойя по этому поводу, лечится просто. Заводим что-то вроде
class TimeProviders
{
   private final EventTimestampSource etSource;
   
   InstantSource systemClock() { return Clock.systemUTC(); }
   InstantSource eventTimestampClock() { return etSource;  }
}

далее в тестах:
when(timeProviders.systemClock()).thenReturn(TestData.SystemClock);
when(timeProviders.eventTimestampClock()).thenReturn(TestData.ETClock);
...
assert someResult1.dateAAA == TestData.SystemClock.now();
...
assert someResult2.dateXYZ == TestData.ETClock.now();


Это, мягко говоря, немного другая ситуация, чем с modify(oldUser, newUser) — т.к. тут по строчке кода никак не видно что куда передаётся и не перепутаны ли параметры.

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

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

P>·>Устанавливаем в тестах, что "у нас на сервере now это 21:00:01, а на клиенте "21:00:02". И в тестах можно ассертить что в нужных случаях ожидаемое время. Что ты будешь делать со своим синлтоном Time.Now — ты так и не рассказал. Вообще я не понимаю как ты такое можешь использовать и тем более тестировать, просвети.

P>Просто разные аргументы.
Аргументы чего? Откуда ты знаешь, что значения этих аргументов именно Time.Now и как это проверишь в тесте?

P>·>Что синглтоны — плохо учат ещё в детском садике, ясельной группе.

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

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

P>·>Ты заявил, что никак не сможешь время протестировать.
P>Мне не надо время тестировать. Мне нужно тестировать логику, в которой один или несколько параметров — время. Вы это время протаскиваете через конструктор косвенно, в виде провайдера. Я протаскиваю через параметр функции, явно.
Надо где-то тестировать, что ты протаскиваешь именно Time.Now а не что-то другое.

P>>>Ну и что?

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

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

P>·>Если при этом нужно плодить копипасту, то это явно что-то не то. И неясно причём тут инлайн и гибкость кода. str.length() — это негибкий код?!
P>Вы сейчас перевираете.
Неперивираю, ты просто свои слова забыл. Напоминаю: " гибкость, которая проявляется в т.ч. в том, что код можно трансформировать, инлайнить". А дальше чистая логика:
"гибкий код → можно инлайнить" => "инлайнить нельзя → не гибкий код" см. https://en.wikipedia.org/wiki/Contraposition

P>Речь про инлайн вашего nextFriday(). Об ваш супер-крутой подход почему то ИДЕ спотыкается и вы проблемы в этом не видите.

Я спрашиваю "зачем инлайн"? Какую задачу-то решаем этим инлайном?!

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

P>·>Рассказал и даже код показал,
Автор: ·
Дата: 28.12.23
насколько понял твою задачу. Если ещё остались вопросы, спрашивай.

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

P>>>И не надо.

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

P>>>Билдера запросов к базе данных. Вы что думаете, все sql запросы руками пишутся?

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

P>О том, что фильтр может вернуть много записей, вам может сказать или сам запрос, или его результат.

Как запрос может что-то сказать?

P>Второй вариант у вас отпадает, т.к. в тестах у вас не будет хрензнаетсколькилион записей в тестах

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

P>>> Кое что, для аналитики — возможно. Но это большей частью всё равно перегоняется в орм или билдер. Есть орм — можно проверять построение логики запросов орм.

P>·>В смысле ты в своих тестах linq/etc тестируешь? Зачем? У linq свои тесты есть.
P>Это вы собираетесь базу тестировать
Опять врёшь. И цитаты, ясен пень, не будет.

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

"Надо" кому? Мы вроде говорим о тестировании ожиданий бизнеса. Ты правда считаешь, что пользователи ожидают определённый выхлоп sql?!

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

P>·>Что за бред? У тебя в FRD прописаны тексты запросов??! Прям где-то у вас есть бизнес-требование "select * from users where id=?" и "in: [fn1]"?! Приходит к вам клиент и говорит "а мне очень нужно чтобы out: [fn2], когда можете сделать?"
P>Что вас смущает? Запрос к бд должен возвращать именно те данные, что прописаны в бл или непосредственно следуют из бл, нефункциональных требований итд. Вас почему то тянет всё к абсурду сводить.
P>Кроме бизнес требований, у нас куча важных вещей — те самые нефункциональные. Их на порядок больше чем бизнес-требований.
P>И всё это нужно покрывать тестами, иначе любой дядел поломает цивилизацию
Ещё раз. У тебя в коде теста, в ожиданиях находится "in: [fn1]" и т.п. Как это соответствует "возвращать именно те данные, что прописаны в бл"? Где в бл вообще будет фигурировать in, fn1 и прочий select?

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

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

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

P>·>Я код
Автор: ·
Дата: 28.12.23
привёл. Что не устраивает-то??! "ищем" — это "find", английский язык знаешь?

P>Смотрите сами: assertThat(testSubject.find(new Filter.byEqual("Vasya"))).isPresent();
P>У вас testSubject это или репозиторий, который вы предлагаете с подключеной бд тестировать
Да.

P>Ни то, ни другое проблему с фильтрами не найдут.

Почему?

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

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

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

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

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

P>А это значит, что ваши тесты контролера идут в топку, как и моки которые вы под это дело прикрутили.
В контроллере надо будет заменить использование systemClock() на eventTimestampClock() и поправить тесты, чтобы зафиксировать функциональное изменение.

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

P>·>Покажи пример бизнес-требования.
P>зачем?
Чтобы понять какое отношение имеет метапрограммирование к тестированию ожиданий бизнес-требований.

P>>>и у вас кейс с вырожденным фильром до сих пор не покрыт тестом.

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

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

P>·>Да причём тут стиль? Это мелкий чих. Я могу переписать например "SELECT ... X OR Y" на "UNION SELECT ... X ... SELECT ... Y" для оптитмизации. У тебя все тесты посыпятся и любая ошибка — увидишь после деплоя и прогона многочасовых тестов, в лучшем случае (а скорее всего только в проде). А у меня только те тесты грохнутся, если я реально ошибусь и что-то поломаю в поведении, ещё до коммита.
P>Очевидно, посыплются не все. Ну нет такой ситуации что бы билдер на каждый запрос лепил UNION. Отвалится скорее всего несколько кейсов, ради которых это все и затевается.
Ну этот кусочек может быть в большой группе запросов, выбирающих разные данные по-разному, но часть логики — общая.

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

P>·>Зато этот код очень простой, линейный и на 99% покрывается компилятором.
P>Не проверяется. Ваш CalendarLogic может принимать разные экземпляры — с текущим временем, клиентским, серверным, из параметра, итд. Всё что вам компилятор скажет, что типы нужные.
P>Ну и что?
P>Кто даст гарантию, что в проде вы правильно прописали зависимости для CalendarLogic? Интерфейс то один и то же
Гарантию, конечно, никто не даст, но покрыть тестами с моками тоже можно. Для этого моки и придумали.

>> Остальной 1% покрывается стартом приложения.

P>Еще меньшая гарантия.
Опять ты о гарантиях заговорил, опять опозоришься.

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

P>·>Я спросил как сделать так, чтобы такие тесты были, ты ответил
Автор: Pauel
Дата: 31.12.23
"Никак.". Т.е. тестов у вас таких просто нет.

P>Вы вопрос свой прочитать в состоянии? как именно ты собираешься в "curl" и-тестах ассертить
P>Буквально curl тест все что может, это сравнить выхлоп, и всё. Если там время не фигурирует, то никак.
Ну, допустим, фигурирует. И чем это тебе поможет?

P>Если же вы имеете ввиду интеграционные, то будет все ровно как и у вас.

С моками? Ну дык об чём спор-то тогда? Ведь именно моком я могу протащить тестовые значения now в нужные места. Там, где ты предлагаешь Time.Now синглтон, в который ты ничего протащить не сможешь.

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

P>·>И это могут быть нефункиональные требования, а какие-то технические детали, оптимизации. Простой пример. Скажем, если идёт выборка по небольшому списку параметров, то строится запрос с "IN (?,?)", иначе создаётся временная табличка и с ней джойнится.
P>Бинго! Оптимизация, нефункциональные требования, технические особенности — все это имеет чудовищное влияние, намного больше, чем сама бизнес-логика.
P>Если вы потеряете оптимизацию — приплызд. и тест нужен зафиксировать эту особенность что бы сохранить при изменениях.
Это уже будет перф-тест и к обсуждаемой теме не относится. Я пока обсуждаю именно и/ю-тесты. И суть в том, что твоё "интеграционные тесты строятся по функциям, а не по запросам к базе" — несостоятельный подход. Т.е. в бизнес-требованиях будет одна функция "выбрать по списку", следовательно и-тест у тебя тоже один, а то что от размера списка может срабатывать разный код, генеряться разные запросы и т.п. — это ты просто не протестируешь и будешь ловить проблемы на проде.

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

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

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

P>·>Ну вроде это уже вс проходили и забили. Заталкивать тесты в прод-код... зачем??!
P>Затем, что это быстрый, дешовый и надежный способ узнавать о проблемах с приложением в целом
P>Покажите, как вы все эти кейсы обнаруживать будете.
Возвращаемся к старому спору. Я уже отвечал на это неоднократно: системы мониторинга, health probes и прочие операционные инструменты. Это не тестирование.

P>Полагаю ответ будет "у нас багов на проде нету" ?

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

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

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

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

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

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

P>·>Анализом требовний, кода, данных, етс. Обычный corner case analysis. А как вы будете искать?
P>Вот-вот. Анализом. Только результаты нужно зафиксировать тестами таким образом, что бы следующиее изменение не сломало всё подряд втихую.
Да, верно.

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

P>·>Нет. А вы таки втащили доказательное программированипе в js?
P>Зачем? Доказывать свойства можно и без coq, только результаты нужно выражать в дизайне и подкреплять тестами.


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

P>·>Я потерял суть. О чём речь вообще? Пример в студи. КОД!
P>Вы когда нибудь видели дашборды где фильтры можно выбирать хрензнаетсколькилионом кнопочек и полей ввода?
Насколько мне доводилось видеть, они обычно через & соединяются. Т.е. список независимых фильтров. Сложность возникает когда фильтры между собой взаимосвязаны, но обычно такое избегают, т.к. юзерам такая compexity тоже не под силу, и использовать нормально всё равно не получается.

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

P>·>Верно. В design-first деливерится манифест first.
P>Не просто манифест — а любое описание, абы ваши пиры могли его понять и цикл разработки был быстрым.
Что значит за любое описание? Манифест это и есть описание api.

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

P>·>Ну да. Откуда же берутся конфиги/тенанты/http-запросы?
P>Появятся после того, как вы заделивирите АПИ, например спустя год, когда у вас уже версия 1.x. И вот здесь вы решите перейти на rust, то в этот момент крайне странно топить за design-first — у нас уже готовое приложение версии 1.x, и нужно сделать так, что бы манифест отрендерился — с учетом тенантов и прочих вещей, включая not implemented. Или вы собираетесь бегать по отделам и рассказывать "а чо вы это делаете — мы же на джаве год назад это решили не делать" ?
У вас уже есть имплементация и мы вот наконец спустя год выкатываем манифест api, но ты всё ещё это называешь design-first?! Нет, это типичный code-first — когда прыгать надо, думать некогда.

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

P>·>Угу. ЧТД. "готовое приложение" == "code-first".
P>Я вам говорю откуда возьмется апи в готовом, если вы решите уже готовое переписать на другом языке.
Причём тут design-first?
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Отредактировано 14.01.2024 19:06 · . Предыдущая версия .
Re[45]: Что такое Dependency Rejection
От: Pauel Беларусь http://blogs.rsdn.org/ikemefula
Дата: 19.01.24 10:33
Оценка: +1
Здравствуйте, ·, Вы писали:

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

·>И пусть добавляются. Это просто способ передачи значений.

Это дополнительные издержки по сравнению с простыми функциями. Вы это сами показали на тестах календаря.

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

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

Ощущение что для вас функциональные требования и функция в джаве это одно и то же. Как вы смоделируете, например, функцию удаления пользователя в системе — дело десятое. Это всего одна строчка в функциональных требованиях. А в вашей архитектуре это может быть процесс растянутый на месяц — пишете в очередь, читаете из нее, удаляет порцию, пишете в очередь... и тд и тд.
И у вас вполне вероятно не будет ни одной функции в джаве "удалить пользователя". А будет например "дропнуть док по хешкоду", "дропнуть записи по номеру телефона", "дропнуть сообщения по емейлу", "отозвать сертификат", "пометить спейс юзера к удалению через месяц", "подготовить выгрузку всех данных юзера", "закинуть зип на aws s3 c ttl 1 месяц"

P>>Нисколько. Моки прибивают вас к конкретному дизайну — см пример про инлайн кода.

·>Вас может прибивают, нас — не прибивают.

Вас тоже прибивают, только вы в этом видите пользу какую то.

P>>Этот пример демонстрирует особенность — даже ИДЕ перестает помогать.

·>Помогать в чём? Ещё раз — инлайнинг и т.п. — это средство, а не цель. С какой целью что инлайнить-то?

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

P>>А вот если вы отказываетесь от моков, соотвественно, ИДЕ вам в помощник — вдруг рефакторинг inline начинает работать сам собой

·>Ок, расскажи от каких моков надо избавиться, чтобы рефакторинг inline для str.length() заработал?

Элементарно — замена вызова на константу. Это работает, т.к. str у вас иммутабельная. А вот текущее время такой особенностью не обладает.

Другая проблема — ваша реализация с зависимостью внутри кешированию не поддаётся.

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

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

·>Зачем?! Это ты из мира js говоришь, где каждую неделю новый фреймворк выходит и надо всё переписать? А каждые пол года очередной самый правильный дизайн появляется от фаулеров и надо срочно всю архитектуру перекраивать?!

Из вас технологический расизм прёт непрерывно. Извините, но на мой взгляд вместе с телепатией это признак неполной профессиональной компетенции.

Дизайн меняется по простой причине — требования меняются непрерывно. Вот появилось нефункциональное требование "перформанс" и мы видим, что CalendarLogic сидит в hotpath и вызывается сто раз с одним и теми же значениями. Опаньки — меняем дизайн. Как это сделать в вашем случае — не ясно. Разве что прокинуть к вашему CalendarLogic еще и MRU, LRU кеш с конфигом "вот такие значения хранить пять минут". Вот уж простор для моков — теперь можно мокать не одно время, а целых три, а можно MRU, LRU обмокать.

P>>Я ж объяснил — таблица истинности рядом с тестом и кодом, моки-стабы тоньше некуда. Вы рассматриваете систему в отрыве от тестов. Это категрически неверно. Даже конкретный деплоймент не имеет смысла без тестов — чемодан без ручки. Нужно проверить, а все ли путём, без тестов это никак. Более того — поменялись внешние зависимости — снова нужны тесты.

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

Наоборот. В прошлом веке правило было "не трогай прод!!!!!!!1111111ййййкуккуку".

P>>Соответсвенно нужно выбирать такое решение, которое проще мейнтейнить, с учетом тестов.

·>Угу. И моки тут помогают.

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

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

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

Как только вы написали тест, то из него однозначно следует, какой будет АПИ и как будут прокидываться зависимости. Вы поймите — код теста это выражение наших намерений
1 апи
2 зависимости
3 поведение
4 связывание
5 входы-выходы.

По большому счету, идеально свести тест к п5. А вы сюда втаскиваете лошадь размером со слона.


P>> 1,2,3,4,5,6 // и так на каждую строчку в табличке

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

·>
·>   when(dep.getX()).thenReturn(x);
·>


Лишняя строчка

·>Связывание компонент это не то же, что связывание запросов с логикой.




P>> Только еще вводите понятие wiring code.

·>Ок. Возможно я использовал неизвестную тебе терминологию. Это из мира управления зависимостей. "wiring up dependencies", "autowiring", под я под wiring code здесь имел в виду "composition root".

Вот-вот. Статься от 2011го, ссылается на книгу, которую начали писать в 00х на основе опыта накопленного тогда же.
Мы только что выяснили, кто же из нас двоих вещает из нулевых

P>>Часть кода связывания будет вне контролера, и правила вы водите сами, это и есть тот дизайн. Прокидываете через dependency injection — значит внутри контролера этого не будет.

·>Да похрен на дизайн. Это лишь меняет место где "волшебство" происходить будет.

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

P>>Прокидываете now в контролере — эту часть логично исключить из dependency injection.

·>В контроллере now откуда возьмётся?

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

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

·>Не понял, чем легче?

Самое важное сразу в коде метода, а не раскидано абы где по всеей иерархии dependency injection.

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

·>Это тут причём? Технически передавая через параметр твой now — тоже может быть null и т.п. Всё то же самое.

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

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


Я ж вам объяснил — тесты прода нужны для самых разных кейсов.

·>А как ты будешь такой и-тест писать, если у тебя будет Time.Now? Что ассертить-то будешь? Как отличать два разных вызова Time.Now — клиентский от серверного? Впрочем, ты уже ответил "никак". Или я что-то пропустил?


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

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

·>Так же можно явно или неявно передать now=null в твой nextFriday(now).

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

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

·>Компилятор может отличать имена символов. Это же в коде видно и на ревью всё очень очевидно — где какое поле куда передаётся.

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

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

·>Какое это имеет отношение к разговору?

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

P>>Просто разные аргументы.

·>Аргументы чего? Откуда ты знаешь, что значения этих аргументов именно Time.Now и как это проверишь в тесте?

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

Компилятор может отличать имена символов. Это же в коде видно и на ревью всё очень очевидно — где какое поле куда передаётся.


P>>·>Что синглтоны — плохо учат ещё в детском садике, ясельной группе.

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

Через параметры функции. Дизайн то плоский, а не глубокий, как у вас.

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

·>Надо где-то тестировать, что ты протаскиваешь именно Time.Now а не что-то другое.

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

P>>Интеграционным, которых ровно столько же, сколько и у вас.

·>Нет. У меня пирамида тестов. А у тебя — либо цилиндр, либо дырявое решето.

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

P>>·>Если при этом нужно плодить копипасту, то это явно что-то не то. И неясно причём тут инлайн и гибкость кода. str.length() — это негибкий код?!

P>>Вы сейчас перевираете.
·>Неперивираю, ты просто свои слова забыл. Напоминаю: " гибкость, которая проявляется в т.ч. в том, что код можно трансформировать, инлайнить". А дальше чистая логика:
·>"гибкий код → можно инлайнить" => "инлайнить нельзя → не гибкий код" см. https://en.wikipedia.org/wiki/Contraposition

С str.length всё в порядке — инлайнится с переводом в константу. У вас что то еще?

P>>Речь про инлайн вашего nextFriday(). Об ваш супер-крутой подход почему то ИДЕ спотыкается и вы проблемы в этом не видите.

·>Я спрашиваю "зачем инлайн"? Какую задачу-то решаем этим инлайном?!

Рефакторинг, оптимизации.

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

·>Ну добавь эту твою кучку в тесты. Вопрос-то в чём?

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

P>>О том, что фильтр может вернуть много записей, вам может сказать или сам запрос, или его результат.

·>Как запрос может что-то сказать?

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

Итого — ваши тесты данных не являются решением, т.к. п 2 у нас обязательный. А вот дополнительные методы, например, инварианты, пред, пост условия, контроль построения запроса, код ревью основанное на понимании работы бд — это дает более-менее осязаемые данные.

P>>Второй вариант у вас отпадает, т.к. в тестах у вас не будет хрензнаетсколькилион записей в тестах

·>Зачем мне столько записей? В простом случае понадобится две записи — та которая я ожидаю, что она должна вернуться и та которая я ожидаю, что не должна вернуться.

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

P>>>> Кое что, для аналитики — возможно. Но это большей частью всё равно перегоняется в орм или билдер. Есть орм — можно проверять построение логики запросов орм.

P>>·>В смысле ты в своих тестах linq/etc тестируешь? Зачем? У linq свои тесты есть.
P>>Это вы собираетесь базу тестировать
·>Опять врёшь. И цитаты, ясен пень, не будет.

Вы топите за косвенные проверки запросов, тестируя связку репозиторий+бд. Вот этого оно и есть.

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

·>"Надо" кому? Мы вроде говорим о тестировании ожиданий бизнеса. Ты правда считаешь, что пользователи ожидают определённый выхлоп sql?!

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

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

·>Ещё раз. У тебя в коде теста, в ожиданиях находится "in: [fn1]" и т.п. Как это соответствует "возвращать именно те данные, что прописаны в бл"? Где в бл вообще будет фигурировать in, fn1 и прочий select?

Не нужно сводить всё к бл напряму. У вас технологического кода в приложении примерно 99%. Он к бл относится крайне опосредовано — через связывание. А тестировать эти 99% тоже нужно.

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

·>Ну я не знаю твой код. Я разрешаю тебе писать кейсы двухстрочные и даже трёхстрочные!

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

P>>Ни то, ни другое проблему с фильтрами не найдут.

·>Почему?

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

P>>т.е. тесты контролера зависят от мока

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

В интеграционном тесте можно сделать то же самое.

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

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

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

P>>А это значит, что ваши тесты контролера идут в топку, как и моки которые вы под это дело прикрутили.

·>В контроллере надо будет заменить использование systemClock() на eventTimestampClock() и поправить тесты, чтобы зафиксировать функциональное изменение.

Функционых изменений здесь ровно 0. С т.з. пользователя никаких функций не добавляется.

P>>зачем?

·>Чтобы понять какое отношение имеет метапрограммирование к тестированию ожиданий бизнес-требований.

Метапрограммирование дает возможность зафиксировать свойства запроса, которые вы с вашими тестами сделать не сможете, никоим образом.

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

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

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

P>>Кто даст гарантию, что в проде вы правильно прописали зависимости для CalendarLogic? Интерфейс то один и то же

·>Гарантию, конечно, никто не даст, но покрыть тестами с моками тоже можно. Для этого моки и придумали.

Вот вот — прибиваете контроллер к тестам на моках.

P>>Буквально curl тест все что может, это сравнить выхлоп, и всё. Если там время не фигурирует, то никак.

·>Ну, допустим, фигурирует. И чем это тебе поможет?

Тогда можно сравнить выхлоп, тот или не тот.

P>>Если же вы имеете ввиду интеграционные, то будет все ровно как и у вас.

·>С моками? Ну дык об чём спор-то тогда? Ведь именно моком я могу протащить тестовые значения now в нужные места. Там, где ты предлагаешь Time.Now синглтон, в который ты ничего протащить не сможешь.

Этим протаскиванием вы бетонируете весь дизайн. Смена дизайна = переписывание тестов.

P>>Бинго! Оптимизация, нефункциональные требования, технические особенности — все это имеет чудовищное влияние, намного больше, чем сама бизнес-логика.

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

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

P>>См выше — все нефунциональные вещи нужно проверять и фиксировать тестами.

·>Это не оправдывает твой подход, что функциональные тесты должны тестировать метапрограммирование.

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

P>>Покажите, как вы все эти кейсы обнаруживать будете.

·>Возвращаемся к старому спору. Я уже отвечал на это неоднократно: системы мониторинга, health probes и прочие операционные инструменты. Это не тестирование.

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

P>>Полагаю ответ будет "у нас багов на проде нету" ?

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

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

P>>насколько я понимаю, это я все еще жду вашего примера про фильтры, который решает ту самую data complexity

·>Какую ту самую? Я никакой "data complexity" не видел и телепатией не обладаю, чтобы выдать какое-то решение для задачи о которой я не слышал.

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

P>> Вход на фильтры полным перебором подавать или магией воздействовать?

·>Да так же, как у тебя. Только ассертить не буквальный текст запроса, а результат его выполнения



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

·>Насколько мне доводилось видеть, они обычно через & соединяются. Т.е. список независимых фильтров. Сложность возникает когда фильтры между собой взаимосвязаны, но обычно такое избегают, т.к. юзерам такая compexity тоже не под силу, и использовать нормально всё равно не получается.

Ну да, вы сами чего то додумали, и воюеете за это

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

P>>Не просто манифест — а любое описание, абы ваши пиры могли его понять и цикл разработки был быстрым.

·>Что значит за любое описание? Манифест это и есть описание api.

Любой формат, любое расширение файла У ваших пиров должен быть инструмент для использования такого описания.
Например, вы решили выдать swagger, но описали в ём такое апи, которое ни один генератор кода не понимает. Это значит, что вы АПИ вообще не предоставили — мало кто в своём уме будет вручную имплементать нюансы которые к релизу поменяются 100500 раз

P>>Появятся после того, как вы заделивирите АПИ, например спустя год, когда у вас уже версия 1.x. И вот здесь вы решите перейти на rust, то в этот момент крайне странно топить за design-first — у нас уже готовое приложение версии 1.x, и нужно сделать так, что бы манифест отрендерился — с учетом тенантов и прочих вещей, включая not implemented. Или вы собираетесь бегать по отделам и рассказывать "а чо вы это делаете — мы же на джаве год назад это решили не делать" ?

·>У вас уже есть имплементация и мы вот наконец спустя год выкатываем манифест api, но ты всё ещё это называешь design-first?! Нет, это типичный code-first — когда прыгать надо, думать некогда.

Вы похоже не читаете. Какой смысл вам что либо рассказывать?
1. когда стартуем проект, где центральная часть это АПИ. Идем через design-first, формат файла и расширение — любое, см. выше
2. готовый проект — уже поздно топить за design-first, просто генерим артефакты
3. переписываем проект на rust — снова, нам design-first не нужен, это вариация предыдущего кейса

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

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

P>>·>Угу. ЧТД. "готовое приложение" == "code-first".
P>>Я вам говорю откуда возьмется апи в готовом, если вы решите уже готовое переписать на другом языке.
·>Причём тут design-first?

У вас точно меняется контингент, или в голове, или за компом:

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

Переписывание предполагает, что у нас уже есть и АПИ, и реализация этого АПИ. Отсюда ясно, что проще сгенерировать артефакт, чем изначально держаться за конкретный формат и топить за design-first
Отредактировано 19.01.2024 12:03 Pauel . Предыдущая версия .
Re[46]: Что такое Dependency Rejection
От: · Великобритания  
Дата: 19.01.24 17:59
Оценка:
Здравствуйте, Pauel, Вы писали:

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

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

P>>>То-то и оно. Вот вы снова сами себе и ответили

P>·>Причём тут тогда "такую функцию"? Интеграция это не "такая функция".
P>Ощущение что для вас функциональные требования и функция в джаве это одно и то же. Как вы смоделируете, например, функцию удаления пользователя в системе — дело десятое. Это всего одна строчка в функциональных требованиях. А в вашей архитектуре это может быть процесс растянутый на месяц — пишете в очередь, читаете из нее, удаляет порцию, пишете в очередь... и тд и тд.
P>И у вас вполне вероятно не будет ни одной функции в джаве "удалить пользователя". А будет например "дропнуть док по хешкоду", "дропнуть записи по номеру телефона", "дропнуть сообщения по емейлу", "отозвать сертификат", "пометить спейс юзера к удалению через месяц", "подготовить выгрузку всех данных юзера", "закинуть зип на aws s3 c ttl 1 месяц"
Ну будет же где-то какой-то код, который отвечает за бизнес-действие "удалить пользователя", который в итоге сделает всё вот это перечисленное. Почему этот код нельзя собрать в одну языковую сущность и назвать "удалятором пользователей", вызать оттуда эти 7 шагов в нужном порядке с нужными параметрами, покрыть тестами — я не понимаю. В твоём мире ты, наверно, в чатике клиентам пошлёшь инструкцию из этих 7 шагов, да? Поэтому такого кода у тебя не будет, да? Или как?

P>·>Вас может прибивают, нас — не прибивают.

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

P>>>Этот пример демонстрирует особенность — даже ИДЕ перестает помогать.

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

P>>>А вот если вы отказываетесь от моков, соотвественно, ИДЕ вам в помощник — вдруг рефакторинг inline начинает работать сам собой

P>·>Ок, расскажи от каких моков надо избавиться, чтобы рефакторинг inline для str.length() заработал?
P>Элементарно — замена вызова на константу. Это работает, т.к. str у вас иммутабельная. А вот текущее время такой особенностью не обладает.
На какую константу? Значение length() зависит от того, что лежит в данном конкретном экземляре str:
Result validateUsername(String username)
{
   if(username.length() > 100) return Error("Too long!");
   else return Ok();
}

Давай, покажи свой супер-полезный инлайн рефакоринг!

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

Кешированию чего? Куда прикручивается кеш — надо смотреть на полное решение, а не на данную конкретнную строчку кода. В твоём решении проблема с кешем будет в другом месте, и её решать будет гораздо сложнее. Точнее ты вообще не сможешь кеш прикрутить, т.к. аргумент now — это текущее время, ВНЕЗАПНО. Ты ключoм в кеше будешь делать текущее время??
А у меня всё круто, т.к. реализация nextFriday() знает, что это отностися к следующей пятнице, то может кешировать одно и то же значение в течение недели и обновлять его по таймеру, например.

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

Ага-ага. Бревно в глазу не замечаешь.

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

P>·>Зачем?! Это ты из мира js говоришь, где каждую неделю новый фреймворк выходит и надо всё переписать? А каждые пол года очередной самый правильный дизайн появляется от фаулеров и надо срочно всю архитектуру перекраивать?!
P>Из вас технологический расизм прёт непрерывно. Извините, но на мой взгляд вместе с телепатией это признак неполной профессиональной компетенции.
P>Дизайн меняется по простой причине — требования меняются непрерывно. Вот появилось нефункциональное требование "перформанс" и мы видим, что CalendarLogic сидит в hotpath и вызывается сто раз с одним и теми же значениями. Опаньки — меняем дизайн.
Не меняем дизайн, а оптимизируем внутреннюю реализацию одного метода, ну того конкретного nextFriday(). Менять дизайн на каждый чих — признак полной профессиональной некомпетенции дизайнера решения. Разуй глаза — в сигнатуре "LocalDate nextFriday()" — совершенно не говорится что откуда должно браться — считаться на лету, из кеша, запрашиваться у пользователя или рандомом генериться от погоды на марсе.

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

Толи ты вообще в дизайн не умеешь, толи нарочно какую-то дичь сочиняешь, чтобы мне приписать для strawman fallacy.

P>>>Соответсвенно нужно выбирать такое решение, которое проще мейнтейнить, с учетом тестов.

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

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

P>·>ТДД диктует необходимость того, чтобы код был тестируемый, а не про то куда как тянуть зависимости. Единственно что он может диктовать, что зависимостями надо управлять явно, чтобы можно было прокидывать тестовые.
P>Как только вы написали тест, то из него однозначно следует, какой будет АПИ и как будут прокидываться зависимости. Вы поймите — код теста это выражение наших намерений
P>1 апи
P>2 зависимости
P>3 поведение
P>4 связывание
P>5 входы-выходы.
P>По большому счету, идеально свести тест к п5. А вы сюда втаскиваете лошадь размером со слона.
апи диктуется требованиями клиентского кода, а не тестами. А зависимости — это уже как мы обеспечиваем работу этого апи.

P>>> 1,2,3,4,5,6 // и так на каждую строчку в табличке

P>·>Не понимаю. Ты, похоже, стиль кода путаешь с дизайном. А дизайн с управлением зависимостями. Ну сделай себе таблицу истинности и с моками, и с параметрами:
P>·>
P>·>   when(dep.getX()).thenReturn(x);
P>·>

P>Лишняя строчка
Прям ужас-ужас. Но это не главное, главное ты наконец-то таки согласился, что твои "таблицы истинности", не пострадали от моков.

P>>> Только еще вводите понятие wiring code.

P>·>Ок. Возможно я использовал неизвестную тебе терминологию. Это из мира управления зависимостей. "wiring up dependencies", "autowiring", под я под wiring code здесь имел в виду "composition root".
P>Вот-вот. Статься от 2011го, ссылается на книгу, которую начали писать в 00х на основе опыта накопленного тогда же.
И? Поэтому я и не понимаю как эта терминология мимо тебя прошла.

P>Мы только что выяснили, кто же из нас двоих вещает из нулевых

Вот от твоего любимого фаулера, свежачок: https://martinfowler.com/articles/dependency-composition.html

P>>>Часть кода связывания будет вне контролера, и правила вы водите сами, это и есть тот дизайн. Прокидываете через dependency injection — значит внутри контролера этого не будет.

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

P>>>Прокидываете now в контролере — эту часть логично исключить из dependency injection.

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

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

P>·>Не понял, чем легче?
P>Самое важное сразу в коде метода, а не раскидано абы где по всеей иерархии dependency injection.
Субъективщина какая-то.

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

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

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

P>Я ж вам объяснил — тесты прода нужны для самых разных кейсов.
Они не "нужны". Есть другие подходы, которые покрывают эти же кейсы (по крайней мере, подавляющее большинство), но не требуют прогона тестов.

P>·>А как ты будешь такой и-тест писать, если у тебя будет Time.Now? Что ассертить-то будешь? Как отличать два разных вызова Time.Now — клиентский от серверного? Впрочем, ты уже ответил "никак". Или я что-то пропустил?

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

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

P>·>Так же можно явно или неявно передать now=null в твой nextFriday(now).
P>Теоретически — можно. Практически — первый интеграционный тест сваливается, когда вызывает этот контроллер.
Если этот тест не забудут написать, конечно. Покрытие-то ты анализировать отказываешься...

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

P>·>Компилятор может отличать имена символов. Это же в коде видно и на ревью всё очень очевидно — где какое поле куда передаётся.
P>Вот вы сами себе и ответили. Ну или объясните, почему код ревью перестанет работать с параметром в контроллере.
По ревью контроллера не видно какой параметр должен туда передаваться и можно ли или нельзя ли туда загонять null. Вот тут:
load: () => repo1,
load2: () => svc,

Почему один load должен быть такой, а другой сякой? А null туда можно или нет? И т.п. И это всё технические детали внутренней реализации, которые к бизнес-требованиям отношения не имеет. И это всё прод-код, покрытый какими-то тестами, которые неясно как относятся к этому коду и выполняют ли его вообще.
В моём случае:
when(timeProviders.systemClock()).thenReturn(TestData.SystemClock);

я тут явно вижу, что системные часы возвращают именно тестовое системное время.
assert someResult1.dateAAA == TestData.SystemClock.now();

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

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

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

P>>>Просто разные аргументы.

P>·>Аргументы чего? Откуда ты знаешь, что значения этих аргументов именно Time.Now и как это проверишь в тесте?
P>С вашего аккаунта похоже разные люди пишут, да еще и меняются во время написания одного и того же сообщения. Вот некто с вашего аккаунта чуть выше ответил на ваш вопрос:
P>

P>Компилятор может отличать имена символов. Это же в коде видно и на ревью всё очень очевидно — где какое поле куда передаётся.

И как это проверишь в тесте?
И я написал почему в твоём коде это неочевидно на ревью.

P>>>·>Что синглтоны — плохо учат ещё в детском садике, ясельной группе.

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

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

P>·>Надо где-то тестировать, что ты протаскиваешь именно Time.Now а не что-то другое.
P>Вам тоже надо тестировать что вы привязали в проде пров клиенского времени, а не пров серверного или предзаписаного. Похоже, у вас там снова смена автора на вашем аккаунте.
Точка привязки будет ровно одна. К символьному имени systemClock будет привязка физического серверного источника времени. А все остальные привязки в бизнес-логике будут покрыты тестами: что ожидаемые значения в результатах связаны с тем же именем assert someResult1.dateAAA == TestData.SystemClock.now(). Если очень хочется, то и привязку физического источника времени можно будет проверить одним _системным_ тестом на всю систему, а не на каждый метод контроллера.
У тебя точек привязки — в каждом месте использования nextFriday и никак не тестируется вообще.

P>>>Интеграционным, которых ровно столько же, сколько и у вас.

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

P>>>·>Если при этом нужно плодить копипасту, то это явно что-то не то. И неясно причём тут инлайн и гибкость кода. str.length() — это негибкий код?!

P>>>Вы сейчас перевираете.
P>·>Неперивираю, ты просто свои слова забыл. Напоминаю: " гибкость, которая проявляется в т.ч. в том, что код можно трансформировать, инлайнить". А дальше чистая логика:
P>·>"гибкий код → можно инлайнить" => "инлайнить нельзя → не гибкий код" см. https://en.wikipedia.org/wiki/Contraposition
P>С str.length всё в порядке — инлайнится с переводом в константу. У вас что то еще?
Ну зайнлайнь. Пример кода я тебе выше привёл.

P>>>Речь про инлайн вашего nextFriday(). Об ваш супер-крутой подход почему то ИДЕ спотыкается и вы проблемы в этом не видите.

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

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

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

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

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

P>>>О том, что фильтр может вернуть много записей, вам может сказать или сам запрос, или его результат.

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

P>>>Второй вариант у вас отпадает, т.к. в тестах у вас не будет хрензнаетсколькилион записей в тестах

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

P>>>Это вы собираетесь базу тестировать

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

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

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

P>Вашими тестами эта задача не решается

Я показал как решение этой задачи можно тестировать моими тестами. Ты так и не рассказал как она решается вашими тестами.

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

P>·>Ещё раз. У тебя в коде теста, в ожиданиях находится "in: [fn1]" и т.п. Как это соответствует "возвращать именно те данные, что прописаны в бл"? Где в бл вообще будет фигурировать in, fn1 и прочий select?
P>Не нужно сводить всё к бл напряму. У вас технологического кода в приложении примерно 99%. Он к бл относится крайне опосредовано — через связывание. А тестировать эти 99% тоже нужно.
Именно. Ты меня обвинил в том, что у меня тесты тестируют детали реализации, а на самом деле ты просто с больной головы на здоровую. Но зато у тебя дизайн самый модный!

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

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

P>>>Ни то, ни другое проблему с фильтрами не найдут.

P>·>Почему?
P>Потому, что у вас нет всего множества данных, которые могут приходить от юзеров.
P>Более того — количество данных дает вам такую особенность, как слишком долгое время выполнения запроса.
P>Задача с фильтрами у меня выявила такую вещь — если данных слишком много, то запрос с секунд резко уезжает в минуты.
P>Вот оптимизацию такой вещи закрыть тестами, что бы первый же фикс не сломал всю цивилизацию
Это всё круто. Каким образом твоё "in: [fn1]" обеспечивает тебя всем множеством данных которое может приходить от юзеров?

P>>>т.е. тесты контролера зависят от мока

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

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

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

P>>>А это значит, что ваши тесты контролера идут в топку, как и моки которые вы под это дело прикрутили.

P>·>В контроллере надо будет заменить использование systemClock() на eventTimestampClock() и поправить тесты, чтобы зафиксировать функциональное изменение.
P>Функционых изменений здесь ровно 0. С т.з. пользователя никаких функций не добавляется.
Меняется наблюдаемое поведение и вообще может быть дырой в безопасности. Клиентское время, скажем, можно захакать.
А если в твоём случае systemClock() и eventTimestampClock() — вещи функционально неразличимые, то вообще неясно зачем ты тут с меня трясёшь как я их буду различать.

P>>>зачем?

P>·>Чтобы понять какое отношение имеет метапрограммирование к тестированию ожиданий бизнес-требований.
P>Метапрограммирование дает возможность зафиксировать свойства запроса, которые вы с вашими тестами сделать не сможете, никоим образом.
А ещё я кофе моими тестами сварить не смогу. Что сказать-то хотел?
Напомню, мы сравниваем тестирование ожиданий бизнес-требований твоими тестами с "in: [fn1]" vs моими тестами c "save/find". Так причём тут метапрограммирование?

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

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

P>>>Кто даст гарантию, что в проде вы правильно прописали зависимости для CalendarLogic? Интерфейс то один и то же

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

P>>>Буквально curl тест все что может, это сравнить выхлоп, и всё. Если там время не фигурирует, то никак.

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

P>>>Если же вы имеете ввиду интеграционные, то будет все ровно как и у вас.

P>·>С моками? Ну дык об чём спор-то тогда? Ведь именно моком я могу протащить тестовые значения now в нужные места. Там, где ты предлагаешь Time.Now синглтон, в который ты ничего протащить не сможешь.
P>Этим протаскиванием вы бетонируете весь дизайн. Смена дизайна = переписывание тестов.
Нет.

P>>>Бинго! Оптимизация, нефункциональные требования, технические особенности — все это имеет чудовищное влияние, намного больше, чем сама бизнес-логика.

P>>>Если вы потеряете оптимизацию — приплызд. и тест нужен зафиксировать эту особенность что бы сохранить при изменениях.
P>·>Это уже будет перф-тест и к обсуждаемой теме не относится.
P>Покрыть перформанс тестами все вырожденные кейсы вам время жизни вселенной не позволит. Некоторые детали реализации нужно фиксировать обычыными тестами.
Обычные тесты не могут тестировать перф. А детали реализации лучше не тестировать.

P>>>См выше — все нефунциональные вещи нужно проверять и фиксировать тестами.

P>·>Это не оправдывает твой подход, что функциональные тесты должны тестировать метапрограммирование.
P>Метапрограммирование тестируется там, где оно применяется в силу естественных причин. Если у вас запросы тривиальные, вам такое не нужно.
Ты не понял. Надо тестировать не метапрограммирование, а ожидания.

P>>>Покажите, как вы все эти кейсы обнаруживать будете.

P>·>Возвращаемся к старому спору. Я уже отвечал на это неоднократно: системы мониторинга, health probes и прочие операционные инструменты. Это не тестирование.
P>Неэффективно. Для многих приложений час невалидной работы может стоит любых денег. Дешевле запустить тесты на проде — час-два и у вас информация по всем функциям.
Ну никто нам не позволит делать "тестовые" сделки на прод системах. Каждая сделка — это реальные деньги и реальная отчётность перед регуляторами. Так что "информацию по всем функциям" нам приходится выдавать ещё до аппрува релиза.

P>>>Полагаю ответ будет "у нас багов на проде нету" ?

P>·>Вот таких багов "протух-обновился сертификат" — и не должно быть. Такие вещи именно мониторингом отслеживаются заранее, чтобы пофиксить можно было до возникновения этой ситуации. А у вас серты протухают ВНЕЗАПНО, и обнаруживаются только тестами постфактум, что у вас в проде что-то где-то отвалилось... ну могу только пособолезновать вашим клиентам.
P>Вы снова включили телепатию. Сертификат может обновиться не у вашего приложения, а у какого нибудь из внешних сервисов. Мониторинг вам скажет, когда N юзеров напорются на это и у вас начнет расти соответсвующая метрика.
Нет, мониторинг нам скажет, что сессия с таким-то сервисом сломалась и свистайте всех на верх.
Такое — major incident и соответствующий разбор полётов с пересмотром всех процессов — как такое допустили и определение действий, чтобы такое больше не произошло.

P>>>насколько я понимаю, это я все еще жду вашего примера про фильтры, который решает ту самую data complexity

P>·>Какую ту самую? Я никакой "data complexity" не видел и телепатией не обладаю, чтобы выдать какое-то решение для задачи о которой я не слышал.
P>У вас есть иллюзия, что ваши тесты гарантируют отсутствие лишнего выхлопа на данных, которые у вас отсутсвуют.
У меня такой иллюзии нет, ты опять насочинял, я такое нигде не писал. О гарантиях в тестах фантазировать любишь только ты.

P>>> Вход на фильтры полным перебором подавать или магией воздействовать?

P>·>Да так же, как у тебя. Только ассертить не буквальный текст запроса, а результат его выполнения
P>
Угу, вот так вот просто.

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

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

P>>>Не просто манифест — а любое описание, абы ваши пиры могли его понять и цикл разработки был быстрым.

P>·>Что значит за любое описание? Манифест это и есть описание api.
P>Любой формат, любое расширение файла У ваших пиров должен быть инструмент для использования такого описания.
P>Например, вы решили выдать swagger, но описали в ём такое апи, которое ни один генератор кода не понимает. Это значит, что вы АПИ вообще не предоставили — мало кто в своём уме будет вручную имплементать нюансы которые к релизу поменяются 100500 раз
Ну я хотя бы могу начать допиливать этот самый swagger, чтобы генератор таки стал понимать.
А у тебя всё гораздо хуже. Ты пишешь аннотации, конфиги, тенантов, реализацию, веб-сервер, потом у тебя наконец-то http-запрос выдаётся этот же самый swagger по этим аннотациям, который ВНЕЗАПНО ни один генератор кода не понимает. И это реальная ситуация, которую помнится уже обсуждали пару лет назад и твоим решением было "ну пусть клиенты пишут всё ручками", т.к. перепилить свои аннотации и http-запрос ты уже не сможешь, ибо проще пристрелить.

P>>>Появятся после того, как вы заделивирите АПИ, например спустя год, когда у вас уже версия 1.x. И вот здесь вы решите перейти на rust, то в этот момент крайне странно топить за design-first — у нас уже готовое приложение версии 1.x, и нужно сделать так, что бы манифест отрендерился — с учетом тенантов и прочих вещей, включая not implemented. Или вы собираетесь бегать по отделам и рассказывать "а чо вы это делаете — мы же на джаве год назад это решили не делать" ?

P>·>У вас уже есть имплементация и мы вот наконец спустя год выкатываем манифест api, но ты всё ещё это называешь design-first?! Нет, это типичный code-first — когда прыгать надо, думать некогда.
P>Вы похоже не читаете. Какой смысл вам что либо рассказывать?
P>1. когда стартуем проект, где центральная часть это АПИ. Идем через design-first, формат файла и расширение — любое, см. выше
P>2. готовый проект — уже поздно топить за design-first, просто генерим артефакты
P>3. переписываем проект на rust — снова, нам design-first не нужен, это вариация предыдущего кейса
Не понял, как из 1 получилось 2?

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

P>Правда, в этом случае я более чем уверен, вы дольше всего будете подбирать генераторы кода под это АПИ, или выписывать все загогулины руками с воплями "я уже заимплементал херли вы перенесли параметр из квери в хидеры!!!!!!!11111йййкукуку"
Ну я о чём и говорю. У вас "думать некогда, прыгать надо". Пишете аннотации, код, выкатываетет, потом как всегда ВНЕЗАПНО "поздно топить за design-first"

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

P>·>Причём тут design-first?
P>У вас точно меняется контингент, или в голове, или за компом:
P>

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

P>Переписывание предполагает, что у нас уже есть и АПИ, и реализация этого АПИ. Отсюда ясно, что проще сгенерировать артефакт, чем изначально держаться за конкретный формат и топить за design-first
Ага, верно, проще прыгать чем думать. Не спорю.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Отредактировано 19.01.2024 18:34 · . Предыдущая версия .
Re[47]: Что такое Dependency Rejection
От: Pauel Беларусь http://blogs.rsdn.org/ikemefula
Дата: 20.01.24 18:49
Оценка:
Здравствуйте, ·, Вы писали:

P>>Это дополнительные издержки по сравнению с простыми функциями. Вы это сами показали на тестах календаря.

·>Ты просто пытаешься ввести в заблуждение. Да, у меня в тестах добавляется одна строчка кода и собственно всё. Зато в твоём решении добавляется N-строчек в _прод_ коде и требуется больше тестов. Иными словами, ты "убираешь" код, но стесняешься говорить _куда_ этот код убирается.

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

P>>И у вас вполне вероятно не будет ни одной функции в джаве "удалить пользователя". А будет например "дропнуть док по хешкоду", "дропнуть записи по номеру телефона", "дропнуть сообщения по емейлу", "отозвать сертификат", "пометить спейс юзера к удалению через месяц", "подготовить выгрузку всех данных юзера", "закинуть зип на aws s3 c ttl 1 месяц"

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

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

P>>·>Помогать в чём? Ещё раз — инлайнинг и т.п. — это средство, а не цель. С какой целью что инлайнить-то?

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

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

P>>Элементарно — замена вызова на константу. Это работает, т.к. str у вас иммутабельная. А вот текущее время такой особенностью не обладает.

·>На какую константу? Значение length() зависит от того, что лежит в данном конкретном экземляре str:

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

·> Кешированию чего? Куда прикручивается кеш — надо смотреть на полное решение, а не на данную конкретнную строчку кода. В твоём решении проблема с кешем будет в другом месте, и её решать будет гораздо сложнее. Точнее ты вообще не сможешь кеш прикрутить, т.к. аргумент now — это текущее время, ВНЕЗАПНО. Ты ключoм в кеше будешь делать текущее время??

·>А у меня всё круто, т.к. реализация nextFriday() знает, что это отностися к следующей пятнице, то может кешировать одно и то же значение в течение недели и обновлять его по таймеру, например.

Другими, вы здесь предложили наивную реализацию LRU кеша c контролем ттл по таймеру.

P>>Дизайн меняется по простой причине — требования меняются непрерывно. Вот появилось нефункциональное требование "перформанс" и мы видим, что CalendarLogic сидит в hotpath и вызывается сто раз с одним и теми же значениями. Опаньки — меняем дизайн.

·> Не меняем дизайн, а оптимизируем внутреннюю реализацию одного метода, ну того конкретного nextFriday(). Менять дизайн на каждый чих — признак полной профессиональной некомпетенции дизайнера решения. Разуй глаза — в сигнатуре "LocalDate nextFriday()" — совершенно не говорится что откуда должно браться — считаться на лету, из кеша, запрашиваться у пользователя или рандомом генериться от погоды на марсе.

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

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

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

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

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

·>Моки — это ещё один дополнительный инструмент в копилку. Больше инструментов — больще возможностей решить задачу более эффективно.

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

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

P>>1 апи
P>>2 зависимости
P>>3 поведение
P>>4 связывание
P>>5 входы-выходы.
P>>По большому счету, идеально свести тест к п5. А вы сюда втаскиваете лошадь размером со слона.
·>апи диктуется требованиями клиентского кода, а не тестами. А зависимости — это уже как мы обеспечиваем работу этого апи.

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

P>>Лишняя строчка

·>Прям ужас-ужас. Но это не главное, главное ты наконец-то таки согласился, что твои "таблицы истинности", не пострадали от моков.

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

P>>Вот-вот. Статься от 2011го, ссылается на книгу, которую начали писать в 00х на основе опыта накопленного тогда же.

·>И? Поэтому я и не понимаю как эта терминология мимо тебя прошла.

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

P>>Мы только что выяснили, кто же из нас двоих вещает из нулевых

·>Вот от твоего любимого фаулера, свежачок: https://martinfowler.com/articles/dependency-composition.html

Именно что свежачок. Можете заглянуть сразу в Summary

By choosing to fulfill dependency contracts with functions rather than classes, minimizing the code sharing between modules and driving the design through tests, I can create a system composed of highly discrete, evolvable, but still type-safe modules. If you have similar priorities in your next project, consider adopting some aspects of the approach I have outlined.


В 00х Фаулер сотоварищи топили за тот подход, за который вы топите 20 лет спустя.

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

·>Я код прошу показать, но ты упорно скрываешь.

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

P>>Самое важное сразу в коде метода, а не раскидано абы где по всеей иерархии dependency injection.

·>Субъективщина какая-то.

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

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

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

У вас там написание кода начинается с деплоя на прод?
Вы узнаете о проблеме во время самого деплоя! А надо во время написания кода которые идут вперемешку с прогном быстрых тестов, и это все за день-неделю-месяц до самого деплоя!

·>Они не "нужны". Есть другие подходы, которые покрывают эти же кейсы (по крайней мере, подавляющее большинство), но не требуют прогона тестов.


Я и вижу — тестируете деплоем на прод.

P>>Согласно бл. А вы как собираетесь отличать два разных вызова, клиентский от серверного? Вам для этого придется намастырить кучу кода с инжекцией в конструктор, только всё важное будет "где то там"

·>Я уже раза три рассказал. Клиентский и серверный будут иметь разный ожидаемый результат.

Ну вот вам и ответ.

P>>Теоретически — можно. Практически — первый интеграционный тест сваливается, когда вызывает этот контроллер.

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

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

·>И как это проверишь в тесте?

·>И я написал почему в твоём коде это неочевидно на ревью.

Это вам неочевидно, т.к. вы топите за другой дизайн. Комбинирование функции в общем случае проще композиции классов. Классы никуда не денутся — одно другому помогает.

P>>Через параметры функции. Дизайн то плоский, а не глубокий, как у вас.

·>Через параметры значения передаются. Откуда они возьмутся-то?

А вы не видите? Весь код перед глазами.

·>У тебя точек привязки — в каждом месте использования nextFriday и никак не тестируется вообще.


Еще раз — точки привязки это часть интеграции, которая тестируется интеграционным тестом. Интеграционные тесты не тестируют кусочки "время прокинуто метод класса куда прокинута бд куда прокинут репозиторий".
Вместо этого интеграция проверяет последовательность, которая вытекает из требований:
— зарезервировать столик на трех человек на имя Коля Петров на следующую пятницу 21.00
— столик на трех человек успешно зарезервирован на имя Коля Петров на 26.01.2024 21.00
— на емейл: столик на трех человек зарезервирован на имя Коля Петров на 26.01.2024 21.00
— пуш нотификация: столик на трех человек зарезервирован на имя Коля Петров на 26.01.2024 21.00
— статус через минуту-час-день: столик на трех человек зарезервирован на имя Коля Петров на 26.01.2024 21.00
— список резервирований: 3 человека, Коля Петров, 26.01.2024 21:00, столик номер 2 рядом со сценой
итд

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


Вы, похоже, без телепатии жить не можете. Вы спросили, сколько времени идут тесты. Я вам ответил, еще и добавил, это все тесты, всего-всего, всех видов. Но вам всё равно телепатия ближе.
Юнит тесты — пару минут от силы. Их до 15тыс ориентировочно. Остальных — от 1000 до 5000 и они занимают 99% времени.

P>>Рефакторинг, оптимизации.

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

Большинство компиляторов умеют инлайнить только тривиальные вещи. Расчищать hot path это по прежнему ваша забота

P>>Эта кучка называется data complexity. Я, например, билдером могу гарантировать, что данные будут запрашиваться постранично. Или что всегда будет фильтр по времени, где период не более трех месяцев.

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

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

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

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

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

P>>Итого — ваши тесты данных не являются решением, т.к. п 2 у нас обязательный. А вот дополнительные методы, например, инварианты, пред, пост условия, контроль построения запроса, код ревью основанное на понимании работы бд — это дает более-менее осязаемые данные.

·>Я не знаю на какой вопрос ты ответил, но я напомню что я спрашивал: "Как запрос может что-то сказать?". Контекст выделил жирным выше.

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

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

·>Как ты это предлагаешь _гарантировать_? И причём тут тесты?

Это ж вы сунете тесты туда, где они не работают. Как гарантировать — см пример номер 100500 чуть выше

P>>>>Это вы собираетесь базу тестировать

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

Ага, за всё хорошее против всего плохого

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

·>Если ожидания есть, то какая проблема их добавить в тесты?

У вас данных нет. Или вы щас скажете, что у вас уже есть все данные всех юзеров на будущее тысячелетие?

P>>Вашими тестами эта задача не решается

·>Я показал как решение этой задачи можно тестировать моими тестами. Ты так и не рассказал как она решается вашими тестами.

Я сказал, что тестами эта задача не решается. Вы показали совсем другое — что на ваших данных есть решение. А вот есть ли оно на каких то других — а хрен его знает. Тестами не проверить, т.к. еще не родились юзеры которые создадут эти данные.
Если задача посчитать how many beans make five, то пожалуйста — ваш подход работать будет
А если чтото сложнее — то к тестам не сводится. Именно по этому нужно доказательство корректности алгоритма, запроса, и тд

Собственно, вы раз за разом утверждаете, что с разработкой алгоритмов не знакомы. Мало накидать реализацию и тесты — нужно обосновать и доказать корректность.

P>>Не нужно сводить всё к бл напряму. У вас технологического кода в приложении примерно 99%. Он к бл относится крайне опосредовано — через связывание. А тестировать эти 99% тоже нужно.

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

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

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


Зачем вам собеседник, если у вас отличный дуэт с вашей телепатией?

P>>Вот оптимизацию такой вещи закрыть тестами, что бы первый же фикс не сломал всю цивилизацию

·>Это всё круто. Каким образом твоё "in: [fn1]" обеспечивает тебя всем множеством данных которое может приходить от юзеров?

Такой тест гарантирует, что всё множество данных будет проходить именно через эту функцию. И если я знаю, что у неё нужные мне свойства(тестами не решается), то результат будет обладать нужными мне свойствами. Например, если функция никогда не возвращает null, то вы можете и не бояться "а вдруг там всё таки null" и сократить hot path

P>>В интеграционном тесте можно сделать то же самое.

·>Как? Код в студию. Вот в куче кода кучи контроллеров где-то у тебя написано Time.Now. Как ты это проверишь в интеграционном тесте что некий результат содержит именно что-то вычисленное от Now, а не что-то другое?

Я вам привел пример про Колю Петрова. Надеюсь, справитесь

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


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

P>>Метапрограммирование дает возможность зафиксировать свойства запроса, которые вы с вашими тестами сделать не сможете, никоим образом.

·>А ещё я кофе моими тестами сварить не смогу. Что сказать-то хотел?
·>Напомню, мы сравниваем тестирование ожиданий бизнес-требований твоими тестами с "in: [fn1]" vs моими тестами c "save/find". Так причём тут метапрограммирование?

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

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

·>Гы. Опять у тебя тесты гарантии дают.

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

P>>Вот вот — прибиваете контроллер к тестам на моках.

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

Щас у вас ведущий сменится, и вы начнете снова отрицать.

P>>Тогда можно сравнить выхлоп, тот или не тот.

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

Смотрите пример про Колю Петрова

P>>Этим протаскиванием вы бетонируете весь дизайн. Смена дизайна = переписывание тестов.

·>Нет.

Вы прибили контролер к тестам на моках. А тут надо процессинг вытеснить в очередь. Приплыли.

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

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

Детали реализации нужно покрывать тестами, что бы первый залётный дятел не разрушил всю цивилизацию:
— оптимизации
— трудноуловимые баги
— секюрити
— всякие другие -ility
— любой сложный код, где data complexity превышает ваше капасити

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

·>Ты не понял. Надо тестировать не метапрограммирование, а ожидания.

Вы снова решили в слова поиграть.

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

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

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

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

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

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

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


Какой процесс вас застрахует от фейла на другой стороне которую вы не контролируте?

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

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

Вы же топите за тесты и утверждаете, что они чего то там решают. Ниже пример этого

P>>·>Да так же, как у тебя. Только ассертить не буквальный текст запроса, а результат его выполнения

P>>
·>Угу, вот так вот просто.

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

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

·>А у тебя всё гораздо хуже. Ты пишешь аннотации, конфиги, тенантов, реализацию, веб-сервер, потом у тебя наконец-то http-запрос выдаётся этот же самый swagger по этим аннотациям, который ВНЕЗАПНО ни один генератор кода не понимает. И это реальная ситуация, которую помнится уже обсуждали пару лет назад и твоим решением было "ну пусть клиенты пишут всё ручками", т.к. перепилить свои аннотации и http-запрос ты уже не сможешь, ибо проще пристрелить.

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

P>>Вы похоже не читаете. Какой смысл вам что либо рассказывать?

P>>1. когда стартуем проект, где центральная часть это АПИ. Идем через design-first, формат файла и расширение — любое, см. выше
P>>2. готовый проект — уже поздно топить за design-first, просто генерим артефакты
P>>3. переписываем проект на rust — снова, нам design-first не нужен, это вариация предыдущего кейса
·>Не понял, как из 1 получилось 2?

Никак — это разные кейсы.

P>>У вас точно меняется контингент, или в голове, или за компом:

P>>

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

P>>Переписывание предполагает, что у нас уже есть и АПИ, и реализация этого АПИ. Отсюда ясно, что проще сгенерировать артефакт, чем изначально держаться за конкретный формат и топить за design-first
·>Ага, верно, проще прыгать чем думать. Не спорю.

У нас кейс "переписать апи на rust" был 0 раз за 10 лет. Смена технологий от балды — тоже 0 раз. А у вас иначе, каждый месяц всё с нуля на rust переписываете?
Отредактировано 21.01.2024 6:21 Pauel . Предыдущая версия .
Re[48]: Что такое Dependency Rejection
От: · Великобритания  
Дата: 22.01.24 12:04
Оценка:
Здравствуйте, Pauel, Вы писали:

P>>>Это дополнительные издержки по сравнению с простыми функциями. Вы это сами показали на тестах календаря.

P>·>Ты просто пытаешься ввести в заблуждение. Да, у меня в тестах добавляется одна строчка кода и собственно всё. Зато в твоём решении добавляется N-строчек в _прод_ коде и требуется больше тестов. Иными словами, ты "убираешь" код, но стесняешься говорить _куда_ этот код убирается.
P>Чего стесняться — я вам прямо говорю. Переносим код связывания из размытого dependency injection в контроллер
Именно. И по итогу получается, что издержек у тебя больше, строчек кода добавляется огромная куча.

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

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

P>>>И у вас вполне вероятно не будет ни одной функции в джаве "удалить пользователя". А будет например "дропнуть док по хешкоду", "дропнуть записи по номеру телефона", "дропнуть сообщения по емейлу", "отозвать сертификат", "пометить спейс юзера к удалению через месяц", "подготовить выгрузку всех данных юзера", "закинуть зип на aws s3 c ttl 1 месяц"

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

P>>>·>Помогать в чём? Ещё раз — инлайнинг и т.п. — это средство, а не цель. С какой целью что инлайнить-то?

P>>>Рефакторинг, очевидно. Его не раз в год нужно делать, а непрерывно — требования меняются постоянно.
P>·>Опять путаешь цель и средство. Рефакторинг — это средство.
P>Именно что средство.
Дядя Петя... на какой вопрос ты сейчас ответил? Напомню вопрос который я задал: "инлайнинг и т.п. — это средство, а не цель. С какой целью что инлайнить-то?"

P>>>Элементарно — замена вызова на константу. Это работает, т.к. str у вас иммутабельная. А вот текущее время такой особенностью не обладает.

P>·>На какую константу? Значение length() зависит от того, что лежит в данном конкретном экземляре str:
P>Length это интринсик,
Это где и с какого бодуна?

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

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

P>·> Кешированию чего? Куда прикручивается кеш — надо смотреть на полное решение, а не на данную конкретнную строчку кода. В твоём решении проблема с кешем будет в другом месте, и её решать будет гораздо сложнее. Точнее ты вообще не сможешь кеш прикрутить, т.к. аргумент now — это текущее время, ВНЕЗАПНО. Ты ключoм в кеше будешь делать текущее время??

P>·>А у меня всё круто, т.к. реализация nextFriday() знает, что это отностися к следующей пятнице, то может кешировать одно и то же значение в течение недели и обновлять его по таймеру, например.
P>Другими, вы здесь предложили наивную реализацию LRU кеша c контролем ттл по таймеру.
Угу. С нулевым изменением дизайна.

P>>>Дизайн меняется по простой причине — требования меняются непрерывно. Вот появилось нефункциональное требование "перформанс" и мы видим, что CalendarLogic сидит в hotpath и вызывается сто раз с одним и теми же значениями. Опаньки — меняем дизайн.

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

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

Кеш — это отдельный компонент.

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

P>·>Толи ты вообще в дизайн не умеешь, толи нарочно какую-то дичь сочиняешь, чтобы мне приписать для strawman fallacy.
P>Смотрите выше — вы сами, безо всякой моей подсказки предложили "может кешировать одно и то же значение в течение недели и обновлять его по таймеру, например" что есть тот самый кеш.
Именно. А вот ты даже это не смог предложить какого-либо работающего решения, кешировать с ключом по now — это полный бред. И вот тебе придётся для введения кеша в твоём "более гибком коде" перелопатить весь дизайн и тесты, т.к. связывание now и nextFriday у тебя происходит во многих местах и везде надо будет проталкивать этот кеш и его настройки.

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

P>·>Моки — это ещё один дополнительный инструмент в копилку. Больше инструментов — больще возможностей решить задачу более эффективно.
P>Моки в большинстве случаев морозят код, т.е. фиксируют код, бетонируют на времена. Вообще всё что вы пишете в тестах, обладает таким свойством
P>Хотите гибкости — в тесты нужно выносить самый минимум. Как правило это параметры и возвращаемое значение. Вы почему то в этот минимум добавляете все зависимости с их собственным апи.
Я не вижу в этом проблему. Ничего не бетонируется. Всё так же рефакторится. Ещё раз напоминаю, что класс с зависимостями это технически та же функция "параметры и возвращаемое значение", но с ЧПФ. Просто некоторые параметры идут через первое "применение": f(p1, p2) <=> new F(p1).apply(p2).

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

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

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

Не отлит, всегда можно рефакторить.

P>>>Лишняя строчка

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

P>>>Вот-вот. Статься от 2011го, ссылается на книгу, которую начали писать в 00х на основе опыта накопленного тогда же.

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

P>>>Мы только что выяснили, кто же из нас двоих вещает из нулевых

P>·>Вот от твоего любимого фаулера, свежачок: https://martinfowler.com/articles/dependency-composition.html
P>Именно что свежачок. Можете заглянуть сразу в Summary
P>

P>By choosing to fulfill dependency contracts with functions rather than classes

За деревьями леса не видишь... ты код-то погляди, то же самое частичное применение функций для эмуляции "классов".

P>В 00х Фаулер сотоварищи топили за тот подход, за который вы топите 20 лет спустя.

Потому что тогда он показывал это на языках 00х, а сейчас ровно то же самое показывает на typescript. Та же ж, вид в профиль. Неужели ты до сих пор не въехал в ЧПФ?!

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

P>·>Я код прошу показать, но ты упорно скрываешь.
P>Не валяйте дурака — я вам еще на прошлом заходе давал два варианта
P>1. из реквеста
P>2. руками вкидываете
P>С тех пор ничего не изменилось.
Именно. Кода как не было, так и нет. Так как если появится код, так сразу станет очевидно, что лишних строчек у тебя гораздо больше, да ещё и фреймворки какие-то требуются.

P>Собственно, вам ни один из них не нравится, потому вы здесь и выступаете. А щас вот пишете, что ничего не видели.

Именно, кода не видел, т.к. не было.

P>>>Самое важное сразу в коде метода, а не раскидано абы где по всеей иерархии dependency injection.

P>·>Субъективщина какая-то.
P>Это никакая не субъективщина. Все что функционально влияет на контроллер, должно связываться прямо в нем. А вот разная хрень пусть проходит через зависимости, ну вот сериализатор тот же.
P>Т.е. четкое разделение — функциональные вещи и технологические, а у вас в dependency injection всё подряд — "тут я шью, тут я пью, тут селедку заворачиваю"
У тебя проблема даже чётко отделить функциональные вещи от технологических... и не только у тебя. Поэтому более вменяемым и _измеримым_ критерием будет именно разделение по ресурсам — треды/файлы/сеть/etc — отделяем.

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

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

P>Вы узнаете о проблеме во время самого деплоя!

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

P>А надо во время написания кода которые идут вперемешку с прогном быстрых тестов, и это все за день-неделю-месяц до самого деплоя!

Ну чтобы какой-либо тест прогнать — надо стартовать некий код... не? Вот до выполнения первого попавшегося @Test-метода оно и грохнется, где-нибудь в @SetUp.

P>·>Они не "нужны". Есть другие подходы, которые покрывают эти же кейсы (по крайней мере, подавляющее большинство), но не требуют прогона тестов.

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

P>>>Согласно бл. А вы как собираетесь отличать два разных вызова, клиентский от серверного? Вам для этого придется намастырить кучу кода с инжекцией в конструктор, только всё важное будет "где то там"

P>·>Я уже раза три рассказал. Клиентский и серверный будут иметь разный ожидаемый результат.
P>Ну вот вам и ответ.
Напомню вопрос: А как ты будешь такой и-тест писать, если у тебя будет Time.Now? Что ассертить-то будешь? Как отличать два разных вызова Time.Now — клиентский от серверного?
Вот пришло тебе в ответе в неком поле значение "2024-01-23". как ты в и-тесте отличишь, что это вычислилось от вызова Time.Now на серверной стороне, а не на клиентской?

P>>>Теоретически — можно. Практически — первый интеграционный тест сваливается, когда вызывает этот контроллер.

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

P>·>И как это проверишь в тесте?

Ты в очередной раз проигнорировал неудобный вопрос.

P>·>И я написал почему в твоём коде это неочевидно на ревью.

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

P>>>Через параметры функции. Дизайн то плоский, а не глубокий, как у вас.

P>·>Через параметры значения передаются. Откуда они возьмутся-то?
P>А вы не видите? Весь код перед глазами.
Ты показывал код, нет, там этого не видно.

P>·>У тебя точек привязки — в каждом месте использования nextFriday и никак не тестируется вообще.

P>Еще раз — точки привязки это часть интеграции, которая тестируется интеграционным тестом. Интеграционные тесты не тестируют кусочки "время прокинуто метод класса куда прокинута бд куда прокинут репозиторий".
P>Вместо этого интеграция проверяет последовательность, которая вытекает из требований:
P>- зарезервировать столик на трех человек на имя Коля Петров на следующую пятницу 21.00
Этот пример совершенно никак не отностися к обсуждаемому нами методу nextFriday. Или у тебя в твоём дизайне будет семь методов nextMonday...nextSunday?

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

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

P>Остальных — от 1000 до 5000 и они занимают 99% времени.

Ну вот и говорю хоупейдж. По acc-тестам (селениум, сеть, многопоток, полный фарш) у нас было на порядок больше, выполнялось за 20 минут, правда на небольшом кластере.

P>>>Рефакторинг, оптимизации.

P>·>Рефакторинг это тоже не задача. А инлайнить для оптимизаций только самые ленивые компиляторы в режиме дебага не умеют.
P>Большинство компиляторов умеют инлайнить только тривиальные вещи. Расчищать hot path это по прежнему ваша забота
Так ведь и большинство IDE умеют инлайнить только тривиальные вещи. И на практике обычно получается так, что компилятор может заинлайнить больше, чем IDE. Если в твоём nextFriday будет много кода, с вспомогательными приватными функциями, несколькими точками return, etc, — то IDE тебе скажет: "упс!".

P>>>Эта кучка называется data complexity. Я, например, билдером могу гарантировать, что данные будут запрашиваться постранично. Или что всегда будет фильтр по времени, где период не более трех месяцев.

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

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

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

P>·>Я не знаю на какой вопрос ты ответил, но я напомню что я спрашивал: "Как запрос может что-то сказать?". Контекст выделил жирным выше.

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

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

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

P>Как гарантировать — см пример номер 100500 чуть выше

Где чуть выше?

P>>>Вы топите за косвенные проверки запросов, тестируя связку репозиторий+бд. Вот этого оно и есть.

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

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

P>·>Если ожидания есть, то какая проблема их добавить в тесты?
P>У вас данных нет. Или вы щас скажете, что у вас уже есть все данные всех юзеров на будущее тысячелетие?
И как тобой предложенное "in: [fn1]" решает эту проблему? Или ты опять в сторону разговор уводишь?

P>>>Вашими тестами эта задача не решается

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

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

strawman fallacy это называется, господин словоблуд.

P>>>Не нужно сводить всё к бл напряму. У вас технологического кода в приложении примерно 99%. Он к бл относится крайне опосредовано — через связывание. А тестировать эти 99% тоже нужно.

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

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

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

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

P>Зачем вам собеседник, если у вас отличный дуэт с вашей телепатией?
В каком месте тут телепатия? Билд занимающий часы — это твои слова. И тесты в проде — тоже.

P>>>Вот оптимизацию такой вещи закрыть тестами, что бы первый же фикс не сломал всю цивилизацию

P>·>Это всё круто. Каким образом твоё "in: [fn1]" обеспечивает тебя всем множеством данных которое может приходить от юзеров?
P>Такой тест гарантирует, что всё множество данных будет проходить именно через эту функцию.
Не гарантирует. В коде может быть написано "if(thursdayAfterRain())return {...in: [fn42]...}" и тест это не может обнаружить. Ещё раз — тесты ничего не могут гарантировать.

P>И если я знаю, что у неё нужные мне свойства(тестами не решается), то результат будет обладать нужными мне свойствами. Например, если функция никогда не возвращает null, то вы можете и не бояться "а вдруг там всё таки null" и сократить hot path

Наивный юноша.

P>>>В интеграционном тесте можно сделать то же самое.

P>·>Как? Код в студию. Вот в куче кода кучи контроллеров где-то у тебя написано Time.Now. Как ты это проверишь в интеграционном тесте что некий результат содержит именно что-то вычисленное от Now, а не что-то другое?
P>Я вам привел пример про Колю Петрова. Надеюсь, справитесь
Опять врёшь. Это не код.

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

P>Нет, это не функциональное изменение. Мы просто перенесли процессинг в очередь. С точки зрения пользователя ничего не изменилось.
Я это и сказал. Если тут всё равно серверное время, то да, не функциональное, и тесты не пострадают. Тесты сломаются и должны сломаться если ты заменишь источник серверного времени на источник клиентского времени.

P>·>А ещё я кофе моими тестами сварить не смогу. Что сказать-то хотел?

P>·>Напомню, мы сравниваем тестирование ожиданий бизнес-требований твоими тестами с "in: [fn1]" vs моими тестами c "save/find". Так причём тут метапрограммирование?
P>Я вам объяснил уже много раз. Вы точно понимаете, что такое метапрограммирование?
Я не понимаю какое отношение метапрограммирование имеет к бизнес-требованиям и к тому как писать тесты.

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

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

P>>>Тогда можно сравнить выхлоп, тот или не тот.

P>·>Так я тебя и спрашиваю как ты будешь сравнивать выхлоп зависящий от Time.Now — тот он или не тот?
P>Смотрите пример про Колю Петрова
Не нужен мне пример. Мне нужен код.

P>>>Этим протаскиванием вы бетонируете весь дизайн. Смена дизайна = переписывание тестов.

P>·>Нет.
P>Вы прибили контролер к тестам на моках. А тут надо процессинг вытеснить в очередь. Приплыли.
Ну выплывайте.

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

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

P>- трудноуловимые баги

P>- секюрити
P>- всякие другие -ility
P>- любой сложный код, где data complexity превышает ваше капасити
Это "за всё хорошее, против всего плохого". А секьюрити — это бизнес-требование и обязано быть покрыто фукнциональными тестами.

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

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

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

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

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

Ок, для хоумпейджей можно хоть левой пяткой писать, неважно.

P>>>Вы снова включили телепатию. Сертификат может обновиться не у вашего приложения, а у какого нибудь из внешних сервисов. Мониторинг вам скажет, когда N юзеров напорются на это и у вас начнет расти соответсвующая метрика.

P>·>Нет, мониторинг нам скажет, что сессия с таким-то сервисом сломалась и свистайте всех на верх.
P>А у вас один юзер в день что ли? Пошел трафик, а потом вжик — идут фейлы.
На это и придумали healthchecks (а конкретно liveness probe), проверка, что сервис — operational, явно проверяет себя и свои зависимости, что может отрабатывать трафик без фейлов ещё до того как пошли реальные запросы.

P>·>Такое — major incident и соответствующий разбор полётов с пересмотром всех процессов — как такое допустили и определение действий, чтобы такое больше не произошло.

P>Какой процесс вас застрахует от фейла на другой стороне которую вы не контролируте?
Большая тема. Начни отсюда: https://en.wikipedia.org/wiki/High_availability

P>>>У вас есть иллюзия, что ваши тесты гарантируют отсутствие лишнего выхлопа на данных, которые у вас отсутсвуют.

P>·>У меня такой иллюзии нет, ты опять насочинял, я такое нигде не писал. О гарантиях в тестах фантазировать любишь только ты.
P>Вы же топите за тесты и утверждаете, что они чего то там решают. Ниже пример этого
Решают проблемы в _твоих тестах_.

P>>>

P>·>Угу, вот так вот просто.
P>Вот этот самый пример — вы здесь утверждаете, что тестами решите проблему с data complexity. Или пишете тесты непонятно для чего, или же не в курсе data complexity
Где здесь? Цитату, или опять соврамши.

P>·>Ну я хотя бы могу начать допиливать этот самый swagger, чтобы генератор таки стал понимать.

P>·>А у тебя всё гораздо хуже. Ты пишешь аннотации, конфиги, тенантов, реализацию, веб-сервер, потом у тебя наконец-то http-запрос выдаётся этот же самый swagger по этим аннотациям, который ВНЕЗАПНО ни один генератор кода не понимает. И это реальная ситуация, которую помнится уже обсуждали пару лет назад и твоим решением было "ну пусть клиенты пишут всё ручками", т.к. перепилить свои аннотации и http-запрос ты уже не сможешь, ибо проще пристрелить.
P>Вы наверное с кем то меня путаете. Я ни призывал писать всё руками. С аннотациями похоже снова дело в вашей телепатии. Я ж вам сказал — они для конкретных кейсов. Кодогенерация дает медленный цикл разработки. В некоторых кейсах это можно сократить на недели и месяца. Я привел вам примеры но вы как обычно прошли мимо
Кодогенерация сама по себе медленный цикл разаработки не даёт. Любой кодогенератор работает от силы порядка минут.

P>>>Вы похоже не читаете. Какой смысл вам что либо рассказывать?

P>>>1. когда стартуем проект, где центральная часть это АПИ. Идем через design-first, формат файла и расширение — любое, см. выше
P>>>2. готовый проект — уже поздно топить за design-first, просто генерим артефакты
P>>>3. переписываем проект на rust — снова, нам design-first не нужен, это вариация предыдущего кейса
P>·>Не понял, как из 1 получилось 2?
P>Никак — это разные кейсы.
Ну я за 1 и не люблю 2, а 3 это следствие 2, т.к. "прыгают, а не думают". Об чём спор-то?

P>·>Ага, верно, проще прыгать чем думать. Не спорю.

P>У нас кейс "переписать апи на rust" был 0 раз за 10 лет. Смена технологий от балды — тоже 0 раз. А у вас иначе, каждый месяц всё с нуля на rust переписываете?
C++, kotlin, java, groovy, scala (может ещё что забыл)... И таки да, спеки всё-таки есть в виде FIX или хотя бы avro/protobuf. А когда копадаются другие команды/внешине сервисы с подходом "нагенерим из аннотаций" — вечная головная боль интегрироваться с такими.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[49]: Что такое Dependency Rejection
От: Pauel Беларусь http://blogs.rsdn.org/ikemefula
Дата: 23.01.24 20:02
Оценка:
Здравствуйте, ·, Вы писали:

P>>Чего стесняться — я вам прямо говорю. Переносим код связывания из размытого dependency injection в контроллер

·>Именно. И по итогу получается, что издержек у тебя больше, строчек кода добавляется огромная куча.

Нету никакой огромной кучи

P>>Это я вам пример привожу, что функциональность приложения и функции в джаве это две большие разницы.

·>А теперь тебе осталось продемонстировать, что я когда либо утверждал что это одно и то же, или опять софистикой занимаешься? Я лишь говорил, что функциональность можно выражать в виде функций, в т.ч. и ЧПФ. Вот в твоём привёдённом "контрпримере" для "удалить пользователя", что плохого или невозможного в наличии соответствующей функции в яп и покрытии её тестами?

А нефункционые требования вы будете в виде нефункций выражать? Вы притягивает юзера к вашим тестам. Юзера можно притянуть только к функциональным тестам. Во всех остальных он не фигурирует.

P>>>>Его не раз в год нужно делать, а непрерывно — требования меняются постоянно

.
P>>·>Опять путаешь цель и средство. Рефакторинг — это средство.
P>>Именно что средство.
·>Дядя Петя... на какой вопрос ты сейчас ответил? Напомню вопрос который я задал: "инлайнинг и т.п. — это средство, а не цель. С какой целью что инлайнить-то?"

Я вам подсветил, что бы полегче прочесть было

P>>·>На какую константу? Значение length() зависит от того, что лежит в данном конкретном экземляре str:

P>>Length это интринсик,
·>Это где и с какого бодуна?

length cводится к
1 получению размера внутреннего массива символов что есть неизменная величина
2 гарантии иммутабельности соответсвующей ссылки. Т.е. никакой modify(str) не сможет легально изменить длину строки str
3 джит компилятор в курсе 1 и 2 и инлайнит метод length минуя все слои абстракции, включая сам массив и его свойство
Если вы запилите свой класс строк, нарушив или 1, или 2, компилятор ничего вам не сможет заинлайнить.

ваш провайдер времени не обладает свойствами навроде 1 или 2, потому не то что компилятор, а ИДЕ, а часто и разработчик не смогут просто так заинлайнить метод

P>>его сам компилятор заинлайнит, вам не надо заботиться

P>>Инлайнить самому нужно для оптимизации, изменения дизайна, итд
·>Противоречивые параграфы детектед.

Я вижу здесь другой кейс — вы так торопитесь, что обгоняете сами себя. Инлайнить методы нужно для оптимизации или изменения дизайна. В случае c Length инлайн в целях оптимизации выполнит компилятор.

P>>·>А у меня всё круто, т.к. реализация nextFriday() знает, что это отностися к следующей пятнице, то может кешировать одно и то же значение в течение недели и обновлять его по таймеру, например.

P>>Другими, вы здесь предложили наивную реализацию LRU кеша c контролем ттл по таймеру.
·>Угу. С нулевым изменением дизайна.

Ну да — добавить компонент с конфигом и сайд-эффектом это стало вдруг нулевым изменением дизайна

P>>Запрос из кеша это изменение того самого дизайна.

·>Твоего дизайна — да, моего — нет.

Т.е. вы не в курсе, что значение функции закешировать гораздо проще, чем мастырить LRU кеш? Более того — такую кеширующую функцию еще и протестировать проще.

P>>Кеш, как минимум, надо инвалидировать, может и ттл прикрутить, а может и следить, что именно кешировать, а что — нет. Для этого вам нужно будет добавить кеш, настройки к нему, да еще связать правильно и тестами покрыть — что в ваш компонент приходит тот самый кеш а не просто какой то.

·>Кеш — это отдельный компонент.

Вот-вот. Отдельный компонент. В том то и проблема. И вам все это придется мастырить и протаскивать. Только всегда "где то там"

P>>Смотрите выше — вы сами, безо всякой моей подсказки предложили "может кешировать одно и то же значение в течение недели и обновлять его по таймеру, например" что есть тот самый кеш.

·>Именно. А вот ты даже это не смог предложить какого-либо работающего решения, кешировать с ключом по now — это полный бред. И вот тебе придётся для введения кеша в твоём "более гибком коде" перелопатить весь дизайн и тесты, т.к. связывание now и nextFriday у тебя происходит во многих местах и везде надо будет проталкивать этот кеш и его настройки.

Ровно так же как и у вас, только дизайн не надо перелопачивать.

P>>Хотите гибкости — в тесты нужно выносить самый минимум. Как правило это параметры и возвращаемое значение. Вы почему то в этот минимум добавляете все зависимости с их собственным апи.

·>Я не вижу в этом проблему. Ничего не бетонируется. Всё так же рефакторится. Ещё раз напоминаю, что класс с зависимостями это технически та же функция "параметры и возвращаемое значение", но с ЧПФ. Просто некоторые параметры идут через первое "применение": f(p1, p2) <=> new F(p1).apply(p2).

Вы пока что инлайн обеспечить не можете, а замахиваетесь на чтото большее

P>>Вы регулярно приводите примеры лишних строчек, но почему то вам кажется, что у меня кода больше. Код системы это не только функциональная часть, но тестирующая.

·>Я не привожу лишние строчки. Я привожу полный код, а ты весь код просто не показываешь.

И где ваш код nextFriday и всех вариантов инжекции для разных кейсов — клиенское время, серверное, текущие, такое, сякое ?

P>>Я освоил тот метод именно в нулевых, задолго до той самой статьи. С тех пор много чего эволюционировало. Потому я и пишу вам, что вы продолжаете вещать из нулевых, как будто с тех пор ничего и не было.

·>Теперь определись — толи ты сейчас привираешь, что это тебе всё давно знакомо, толи ты просто словоблудил, выразив непонимание что я имею в виду под wiring.

wiring и есть связывание. Код связывания в dependency injection, который вы называете wiring, и код связывания в контроллере — выполняют одно и то же — интеграцию. Разница отражает подходы к дизайну. И то и другое wiring, и то и другое связывание. И задача решается одна и та же. Только свойства решения разные.


P>>

P>>By choosing to fulfill dependency contracts with functions rather than classes

·>За деревьями леса не видишь... ты код-то погляди, то же самое частичное применение функций для эмуляции "классов".

Похоже, что пример для Буравчика вы не поняли

P>>В 00х Фаулер сотоварищи топили за тот подход, за который вы топите 20 лет спустя.

·>Потому что тогда он показывал это на языках 00х, а сейчас ровно то же самое показывает на typescript. Та же ж, вид в профиль. Неужели ты до сих пор не въехал в ЧПФ?!

То же, да не то же. Посмотрите где связывание у него, а где связывание у вас.

P>>С тех пор ничего не изменилось.

·>Именно. Кода как не было, так и нет. Так как если появится код, так сразу станет очевидно, что лишних строчек у тебя гораздо больше, да ещё и фреймворки какие-то требуются.

Фремворки и у вас есть, только из 00х. Вы уже признавались в этом.

P>>У вас там написание кода начинается с деплоя на прод?

·>Это где я такое написал? Как вообще такое возможно?!

Вы же рассказываете, что проблему увидите на старте приложения. Прод у вас получает конфиг прода и другие вещи прода. Вот на старте и узнаете, что у вас не так пошло. А надо зафейлить тест, когда вы только-только накидали первую версию контроллера — в этот момент у вас буквально приложения еще нет

P>>А надо во время написания кода которые идут вперемешку с прогном быстрых тестов, и это все за день-неделю-месяц до самого деплоя!

·>Ну чтобы какой-либо тест прогнать — надо стартовать некий код... не? Вот до выполнения первого попавшегося @Test-метода оно и грохнется, где-нибудь в @SetUp.

Вы тестируете не @Setup а иерархию вызовов dependency injection, которая у вас появляется, появляется... на старте приложения. И фейлы ждете с этого момента. А надо гораздо раньше.
Смотрите тот вариант что у Фаулера — его окончательный дизайн позволяет вам фейлить многие вещи когда ничего другого вообще нет — его связывание в одном месте и покрывается тестами
У вас аналог этого связывания будет а хрен его знает когда

·>Не знаю где ты это увидел, имеется в виду мы тестируем прод окружение пытаясь деплоить на прод. Вы, кстати, тестируете уже после деплоя на прод.


Не после — а задолго до. После деплоя прогон тестов это расширенный мониторинг — проверяется выполнимость сценариев пользователя, а не просто healthcheck который вы предлагаете
Почему может поломаться — потому, что приложение работает с кучей внешних зависимостей, которые мы не контролируем.

P>>Ну вот вам и ответ.

·>Напомню вопрос: А как ты будешь такой и-тест писать, если у тебя будет Time.Now? Что ассертить-то будешь? Как отличать два разных вызова Time.Now — клиентский от серверного?
·>Вот пришло тебе в ответе в неком поле значение "2024-01-23". как ты в и-тесте отличишь, что это вычислилось от вызова Time.Now на серверной стороне, а не на клиентской?

Смотрите пример про Колю Петрова, там ответ на этот вопрос

P>>Нам нужны именно фичи, кейсы, сценарии. В строчках кода это не измеряется. Смотрите, как это делают QA.

·>Как ты убедишься, что некая фича покрыта тестами? Или что все фичи и разные corner cases описаны в бизнес-требованиях? Покрытие хоть и ничего не гарантирует, но часто помогает обнаружить пробелы.

Я ж говорю — смотрите, как это у QA делается.

P>>- зарезервировать столик на трех человек на имя Коля Петров на следующую пятницу 21.00

·>Этот пример совершенно никак не отностися к обсуждаемому нами методу nextFriday. Или у тебя в твоём дизайне будет семь методов nextMonday...nextSunday?

Примером по Колю Петрова вам показываю, что такое интеграционные тесты. Вы же в них хотите проверять "а должно вот там вызываться!"

P>>Остальных — от 1000 до 5000 и они занимают 99% времени.

·>Ну вот и говорю хоупейдж. По acc-тестам (селениум, сеть, многопоток, полный фарш) у нас было на порядок больше, выполнялось за 20 минут, правда на небольшом кластере.

Подозреваю, вы их просто подробили, "проверим, что время вон то"

P>>Не нравится метапрограммирование — можете придумать что угодно.

·>И что? Причём тут тесты-то?

Тесты где сравнивается выхлоп из базы не решают проблемы data complexity. Отсюда ясно — что нужно добавлять нечто большее.

P>>Вы там ногой читаете? Запрос не человек, сказать не может. А вот ревьюер может сказать "у нас такого индекса нет, тут будет фуллскан по таблице из десятков миллионов записей с выполнением регекспа и время работы на проде надо ждать десятки минут"

·>Или у вас неимоверно крутые ревьюверы, помнят наизусть сотни таблиц и тысячи индексов, или у вас хоумпейдж. Не все могут себе такое позволить.

Зато сразу ясно, где именно хоум пейдж, и почему ваши тесты работают ажно 20 минут.

P>>Это ж вы сунете тесты туда, где они не работают.

·>Это ты словоблудием занимаешься. Мы обсуждаем именно тесты, а ты тут в сторону доказательного программирования и мета уходишь.

Я вам о том, какие гарантии можно получить вашими тестами, а какие — нельзя.
В вашем случае вы ничего не гарантируете на тех данных, что у вас нет.

P>>Как гарантировать — см пример номер 100500 чуть выше

·>Где чуть выше?

Я вам одно и то же пишу второй год.

P>>У вас данных нет. Или вы щас скажете, что у вас уже есть все данные всех юзеров на будущее тысячелетие?

·>И как тобой предложенное "in: [fn1]" решает эту проблему? Или ты опять в сторону разговор уводишь?

Примерно так же, как assert.notnull(parameter) в начале метода или return assert.notEmpty(value) в конце

P>>Наоборот.

·>Ваша цитата: Ага, тесты "как написано" Вам придется написать много больше, чем один тест — как минимум, пространство входов более-менее покрыть. Моками вы вспотеете это покрывать.

Ну ок, значит ничья в этом вопросе

P>>·>Зато есть возможность покрыть известные данные и комбинации тестами. Вы и этого не делаете, а просто имена приватных функций ассертите и буковки сравниваете (тоже, кстати известные). Вот и приходится часами билдить и в проде тесты гонять чтобы хоть что-то как-то обнаружилось.

P>>Зачем вам собеседник, если у вас отличный дуэт с вашей телепатией?
·>В каком месте тут телепатия? Билд занимающий часы — это твои слова. И тесты в проде — тоже.

Вы всё знаете — и сложность проекта, и объемы кода, и начинку тестов, и частоту тех или иных тестов. Просто удивительно, как тонкая телепатия.

P>>Такой тест гарантирует, что всё множество данных будет проходить именно через эту функцию.

·>Не гарантирует. В коде может быть написано "if(thursdayAfterRain())return {...in: [fn42]...}" и тест это не может обнаружить. Ещё раз — тесты ничего не могут гарантировать.

А я и говорю в который раз, что основное совсем не в тестах. В них мы всего лишь фиксируем некоторые свойства решения, что бы досталось потомкам в неискаженном виде.

P>>Я вам привел пример про Колю Петрова. Надеюсь, справитесь

·>Опять врёшь. Это не код.

Почему ж не код? Это в кукумбер переводится практически один к одному.

·>Я это и сказал. Если тут всё равно серверное время, то да, не функциональное, и тесты не пострадают. Тесты сломаются и должны сломаться если ты заменишь источник серверного времени на источник клиентского времени.


А вы точно интеграционные пишите? Чтото мне кажется, вы пишете высокоуровневые юнит-тесты.

·>Я не понимаю какое отношение метапрограммирование имеет к бизнес-требованиям и к тому как писать тесты.

Такое же, как ваши классы, методы, dependency injection итд.

P>>Тесты дают гарантию, что пайплайн устроен нужным мне образом. А свойства результата, следовательно, будут определяться свойствами пайплайна. Тогда вам нужно сосредоточиться на выявлении этих свойств, а не писать одно и то же третий месяц кряду.

·>Тесты не могут давать гарантию.

Тесты именно ради гарантий и пишутся. Только чудес от них ждать не надо.

P>>Смотрите пример про Колю Петрова

·>Не нужен мне пример. Мне нужен код.

Бывает и хуже.

P>>Детали реализации нужно покрывать тестами, что бы первый залётный дятел не разрушил всю цивилизацию:

P>>- оптимизации
·>Это перф-тесты.

Это слишком долго. Фейлы перформанса в большинстве случаев можно обнаруживать прямо во время юнит-тестирования.

P>>- трудноуловимые баги

P>>- секюрити
P>>- всякие другие -ility
P>>- любой сложный код, где data complexity превышает ваше капасити
·>Это "за всё хорошее, против всего плохого". А секьюрити — это бизнес-требование и обязано быть покрыто фукнциональными тестами.

Важно, что бы явные фейлы обнаруживались гораздо раньше старта после деплоя. Тогда будет время подумать, как получить максимум сведений из самого деплоя.

·>"Метапрограммирование тестируется" — зачем?


Тестировать нужно вообще всё, что вам взбредет в голову спроектировать. Тестируюя только крупные куски вы получаете дырявое покрытие

P>>Вы не просто утверждаете это, а идете дальше — утверждаете, что тесты прода это признак наличия багов, не той квалификации и тд.

·>Признак более позднего обнаружения багов.

Они показывают, выполнимы ли сценарии под нагрузкой(при изменениях сети, топологии, итд) или нет.

·>На это и придумали healthchecks (а конкретно liveness probe), проверка, что сервис — operational, явно проверяет себя и свои зависимости, что может отрабатывать трафик без фейлов ещё до того как пошли реальные запросы.


Каким чудом ваш пробы дают ответ, выполнимы или все ключевые сценарии при максимальной нагрузке? Сценарий — это та часть, про Колю Петрова, которая вам непонятна

P>>Какой процесс вас застрахует от фейла на другой стороне которую вы не контролируте?

·>Большая тема. Начни отсюда: https://en.wikipedia.org/wiki/High_availability

Выполнимость сценариев это consistensy всех функций сервиса, а вовсе не high availability, как вы думаете.

P>>Вы наверное с кем то меня путаете. Я ни призывал писать всё руками. С аннотациями похоже снова дело в вашей телепатии. Я ж вам сказал — они для конкретных кейсов. Кодогенерация дает медленный цикл разработки. В некоторых кейсах это можно сократить на недели и месяца. Я привел вам примеры но вы как обычно прошли мимо

·>Кодогенерация сама по себе медленный цикл разаработки не даёт. Любой кодогенератор работает от силы порядка минут.

Именно что сама по себе. Вам надо таскать тот манифест, сихнронизироваться с ним, следить, что бы все имели ту самую версию, тот самый генератор, итд итд
Во многих случаях этого можно избежать

P>>У нас кейс "переписать апи на rust" был 0 раз за 10 лет. Смена технологий от балды — тоже 0 раз. А у вас иначе, каждый месяц всё с нуля на rust переписываете?

·>C++, kotlin, java, groovy, scala (может ещё что забыл)... И таки да, спеки всё-таки есть в виде FIX или хотя бы avro/protobuf. А когда копадаются другие команды/внешине сервисы с подходом "нагенерим из аннотаций" — вечная головная боль интегрироваться с такими.

И ежу понятно — вам максимум с собой легко интегрироваться
Re[50]: Что такое Dependency Rejection
От: · Великобритания  
Дата: 25.01.24 10:05
Оценка:
Здравствуйте, Pauel, Вы писали:

P>>>Чего стесняться — я вам прямо говорю. Переносим код связывания из размытого dependency injection в контроллер

P>·>Именно. И по итогу получается, что издержек у тебя больше, строчек кода добавляется огромная куча.
P>Нету никакой огромной кучи
Потому что ты её скромно скрываешь.

P>>>Это я вам пример привожу, что функциональность приложения и функции в джаве это две большие разницы.

P>·>А теперь тебе осталось продемонстировать, что я когда либо утверждал что это одно и то же, или опять софистикой занимаешься? Я лишь говорил, что функциональность можно выражать в виде функций, в т.ч. и ЧПФ. Вот в твоём привёдённом "контрпримере" для "удалить пользователя", что плохого или невозможного в наличии соответствующей функции в яп и покрытии её тестами?
P>А нефункционые требования вы будете в виде нефункций выражать?
По логике — двойка.

P>Вы притягивает юзера к вашим тестам. Юзера можно притянуть только к функциональным тестам. Во всех остальных он не фигурирует.

Ну вроде о функциональных тестах тут и говорили, об проверках ожиданий пользователей. Ты опять как уж на сковородке по кочкам прыгаешь. Контекст не теряй, ок?

P>.

P>>>·>Опять путаешь цель и средство. Рефакторинг — это средство.
P>>>Именно что средство.
P>·>Дядя Петя... на какой вопрос ты сейчас ответил? Напомню вопрос который я задал: "инлайнинг и т.п. — это средство, а не цель. С какой целью что инлайнить-то?"
P>Я вам подсветил, что бы полегче прочесть было
Ну если для тебя инлайн одной функции — это изменение дизайна приложения, то это твоё очень странное понимание слова "дизайн".

P>>>·>На какую константу? Значение length() зависит от того, что лежит в данном конкретном экземляре str:

P>>>Length это интринсик,
P>·>Это где и с какого бодуна?
P>length cводится к
Ты совершенно неправильно понимаешь интринсики и чем они отличаются от инайлна и уж тем более константности. Мягко говоря, абсолютно разные вещи. Ликбез: https://en.wikipedia.org/wiki/Intrinsic_function
Вот тебе полный список интринзиков jvm, например: https://gist.github.com/apangin/7a9b7062a4bd0cd41fcc

P>1 получению размера внутреннего массива символов что есть неизменная величина

P>2 гарантии иммутабельности соответсвующей ссылки. Т.е. никакой modify(str) не сможет легально изменить длину строки str
P>3 джит компилятор в курсе 1 и 2 и инлайнит метод length минуя все слои абстракции, включая сам массив и его свойство
P>Если вы запилите свой класс строк, нарушив или 1, или 2, компилятор ничего вам не сможет заинлайнить.
Бред, джит великолепно инлайнит length(), например, и у StringBuffer или даже LinkedList, и практически каждый getter/setter. И даже умеет инлайнить виртуальные функции.

P>ваш провайдер времени не обладает свойствами навроде 1 или 2, потому не то что компилятор, а ИДЕ, а часто и разработчик не смогут просто так заинлайнить метод

У тебя опять каша в голове. Напомню конткест: не провайдер времени, а nextFriday() вроде как ты собирался инлайнить.
Но вряд ли это всё заинлайнится, т.к. friday и работа с календарём — довольно сложный код, использующий таймзоны и календари, относительно.

Да, кстати, в качестве ликбеза. Провайдер времени currentTimeMillis() — как раз интринзик, ВНЕЗАПНО!

P>>>его сам компилятор заинлайнит, вам не надо заботиться

P>>>Инлайнить самому нужно для оптимизации, изменения дизайна, итд
P>·>Противоречивые параграфы детектед.
P>Я вижу здесь другой кейс — вы так торопитесь, что обгоняете сами себя. Инлайнить методы нужно для оптимизации или изменения дизайна. В случае c Length инлайн в целях оптимизации выполнит компилятор.
Для оптимизации инлайнят только в в случаях если компилятор слишком тупой (что практически никогда) или случай ужасно нетривиальный (тогда и IDE не справится).

P>·>Угу. С нулевым изменением дизайна.

P>Ну да — добавить компонент с конфигом и сайд-эффектом это стало вдруг нулевым изменением дизайна
Ну да. И? Как nextFriday() был, так и остался. Зачем менять дизайн?

P>>>Запрос из кеша это изменение того самого дизайна.

P>·>Твоего дизайна — да, моего — нет.
P>Т.е. вы не в курсе, что значение функции закешировать гораздо проще, чем мастырить LRU кеш? Более того — такую кеширующую функцию еще и протестировать проще.
Ты издеваешься что-ли? Мы тут конкретный пример рассматриваем. Давай расскажи, как ты nextFriday(now) закешируешь "гораздо проще", где now — это текущее время.

P>>>Кеш, как минимум, надо инвалидировать, может и ттл прикрутить, а может и следить, что именно кешировать, а что — нет. Для этого вам нужно будет добавить кеш, настройки к нему, да еще связать правильно и тестами покрыть — что в ваш компонент приходит тот самый кеш а не просто какой то.

P>·>Кеш — это отдельный компонент.
P>Вот-вот. Отдельный компонент. В том то и проблема. И вам все это придется мастырить и протаскивать. Только всегда "где то там"
Что всё? _Добавить_ новую логику в CalendarLogic, внутри него использовать этот кеш, и собственно всё. В чём _изменение_ дизайна-то? Можно обойтись одним новым приватным полем.

P>>>Смотрите выше — вы сами, безо всякой моей подсказки предложили "может кешировать одно и то же значение в течение недели и обновлять его по таймеру, например" что есть тот самый кеш.

P>·>Именно. А вот ты даже это не смог предложить какого-либо работающего решения, кешировать с ключом по now — это полный бред. И вот тебе придётся для введения кеша в твоём "более гибком коде" перелопатить весь дизайн и тесты, т.к. связывание now и nextFriday у тебя происходит во многих местах и везде надо будет проталкивать этот кеш и его настройки.
P>Ровно так же как и у вас, только дизайн не надо перелопачивать.
У меня как-то так. Было
class CalendarLogic {
   ...
   LocalDate nextFriday() {
      return doSomeComputations(instantSource.now());
   }
}

Стало
class CalendarLogic {
   private volatile LocalDate currentFriday;//вот наш кеш
   constructor... {
      var updater = () => currentFriday = doSomeComputations(instantSource.now());
      timer.schedule(updater, ... weekly);
      updater.run();
   }
   ...
   LocalDate nextFriday() {
      return currentFriday;
   }
}

Т.е. меняется внутренняя реализация компонента и, скорее всего, надо будет заинжектить таймер внутрь CalendarLogic — дополнительный парам в конструктор.
Теперь показывай свой код, если потянешь.

P>>>Хотите гибкости — в тесты нужно выносить самый минимум. Как правило это параметры и возвращаемое значение. Вы почему то в этот минимум добавляете все зависимости с их собственным апи.

P>·>Я не вижу в этом проблему. Ничего не бетонируется. Всё так же рефакторится. Ещё раз напоминаю, что класс с зависимостями это технически та же функция "параметры и возвращаемое значение", но с ЧПФ. Просто некоторые параметры идут через первое "применение": f(p1, p2) <=> new F(p1).apply(p2).
P>Вы пока что инлайн обеспечить не можете, а замахиваетесь на чтото большее
Бла-бла-бла.

P>>>Вы регулярно приводите примеры лишних строчек, но почему то вам кажется, что у меня кода больше. Код системы это не только функциональная часть, но тестирующая.

P>·>Я не привожу лишние строчки. Я привожу полный код, а ты весь код просто не показываешь.
P>И где ваш код nextFriday и всех вариантов инжекции для разных кейсов — клиенское время, серверное, текущие, такое, сякое ?
А что там показывать? Вроде всё очевидно:
var clientCalendarLogic = new CalendarLogic(timeProviders.clientClock());
var serverCalendarLogic = new CalendarLogic(timeProviders.systemClock());
var eventCalendarLogic = new CalendarLogic(timeProviders.eventTimestampClock());

Всё ещё про ЧПФ не догоняешь?

P>>>Я освоил тот метод именно в нулевых, задолго до той самой статьи. С тех пор много чего эволюционировало. Потому я и пишу вам, что вы продолжаете вещать из нулевых, как будто с тех пор ничего и не было.

P>·>Теперь определись — толи ты сейчас привираешь, что это тебе всё давно знакомо, толи ты просто словоблудил, выразив непонимание что я имею в виду под wiring.
P>wiring и есть связывание. Код связывания в dependency injection, который вы называете wiring, и код связывания в контроллере — выполняют одно и то же — интеграцию. Разница отражает подходы к дизайну. И то и другое wiring, и то и другое связывание. И задача решается одна и та же. Только свойства решения разные.
Интересная терминология. Покажи хоть одну статью, где код в методах контроллеров называется wiring.

P>·>За деревьями леса не видишь... ты код-то погляди, то же самое частичное применение функций для эмуляции "классов".

P>Похоже, что пример для Буравчика вы не поняли
Код я понял. Я не понял зачем такой код и что именно он улучшил, я предполагаю этого никто тут не понял. А про ЧПФ и классы было в обсуждаемой тут в начале топика статье.

P>>>В 00х Фаулер сотоварищи топили за тот подход, за который вы топите 20 лет спустя.

P>·>Потому что тогда он показывал это на языках 00х, а сейчас ровно то же самое показывает на typescript. Та же ж, вид в профиль. Неужели ты до сих пор не въехал в ЧПФ?!
P>То же, да не то же. Посмотрите где связывание у него, а где связывание у вас.
Мде... Ну ладно, помогу тебе разобраться коли ты сам не осилил.
Связывание у него модуле "the code that assembles my modules": restaurantRatings/index.ts…export const init, вот погляди на "устаревший" composition root. Как у меня. А контроллер у него по сути класс "createTopRatedHandler = (dependencies: Dependencies)" — это конструктор, в который инжектятся депенденси. Как у меня. Экземпляр контроллера там создаётся в строках 45-47, а чуть выше создаются его зависимости, db там, пул какой-то, етс. Практически один-к-одному как и мой пример выше
Автор: ·
Дата: 05.12.23
, у меня правда упрощённо, т.к. не работающий пример, а код набитый на форуме.

В общем та же самая классика 00х. Одно меня не устраивает, что у него в прод-коде грязь в виде "replaceFactoriesForTest". Не очень понял зачем именно так. Впрочем, это скорее всего объясняется тем, что я использую mockito-фреймворк, и он позволеяет весь этот хлам не делать, а у Фаулера просто код, без тестовых фреймоврков

P>>>С тех пор ничего не изменилось.

P>·>Именно. Кода как не было, так и нет. Так как если появится код, так сразу станет очевидно, что лишних строчек у тебя гораздо больше, да ещё и фреймворки какие-то требуются.
P>Фремворки и у вас есть, только из 00х. Вы уже признавались в этом.
Где именно?

P>>>У вас там написание кода начинается с деплоя на прод?

P>·>Это где я такое написал? Как вообще такое возможно?!
P>Вы же рассказываете, что проблему увидите на старте приложения. Прод у вас получает конфиг прода и другие вещи прода. Вот на старте и узнаете, что у вас не так пошло. А надо зафейлить тест, когда вы только-только накидали первую версию контроллера — в этот момент у вас буквально приложения еще нет
Чтобы зафейлить тест, код надо стартовать. Не всё приложение, ясен пень, зачем для теста конфиг прода, ты в своём уме?! Конфиг прода девам даже не доступен бывает, т.к. там могут быть секреты.

P>>>А надо во время написания кода которые идут вперемешку с прогном быстрых тестов, и это все за день-неделю-месяц до самого деплоя!

P>·>Ну чтобы какой-либо тест прогнать — надо стартовать некий код... не? Вот до выполнения первого попавшегося @Test-метода оно и грохнется, где-нибудь в @SetUp.
P>Вы тестируете не @Setup а иерархию вызовов dependency injection, которая у вас появляется, появляется... на старте приложения. И фейлы ждете с этого момента. А надо гораздо раньше.
Это ты что-то не так понял. Я такое никогда не говорил.

P>Смотрите тот вариант что у Фаулера — его окончательный дизайн позволяет вам фейлить многие вещи когда ничего другого вообще нет — его связывание в одном месте и покрывается тестами

P>У вас аналог этого связывания будет а хрен его знает когда
Ну так у него там вовсю моки (стабы) используются. С поправкой, что это ts с недоразвитыми мок-либами. У него
    const vancouverRestaurants = [
      {
        id: "cafegloucesterid",
        name: "Cafe Gloucester",
      },
      {
        id: "baravignonid",
        name: "Bar Avignon",
      },
    ];

    const topRestaurants = [
      {
        city: "vancouverbc",
        restaurants: vancouverRestaurants,
      },
    ];

    const dependenciesStub = {
      getTopRestaurants: (city: string) => {
        const restaurants = topRestaurants
          .filter(restaurants => {
            return restaurants.city == city;
          })
          .flatMap(r => r.restaurants);
        return Promise.resolve(restaurants);
      },
    };

    const ratingsHandler: Handler =
      controller.createTopRatedHandler(dependenciesStub);

У меня аналогом будет
var repo = mock(Repo.class);
var vancouverRestaurants = List.of(
  new Restaurant("cafegloucesterid", "Cafe Gloucester"),
  new Restaurant("baravignonid", "Bar Avignon"),
);
when(repo.getTopRestaurants("vancouverbc")).thenReturn(vancouverRestaurants);
var ratingsHandler new TopRatedHandler(repo);

Теперь чуешь о чём я говорю что кода меньше?

P>·>Не знаю где ты это увидел, имеется в виду мы тестируем прод окружение пытаясь деплоить на прод. Вы, кстати, тестируете уже после деплоя на прод.

P>Не после — а задолго до. После деплоя прогон тестов это расширенный мониторинг — проверяется выполнимость сценариев пользователя, а не просто healthcheck который вы предлагаете
P>Почему может поломаться — потому, что приложение работает с кучей внешних зависимостей, которые мы не контролируем.
И не можете проверить что зависимости — operational? Пинг послать?
А для проверок consistency — нужно версирование и проверка совместимости версий. Гонять тесты — не _нужно_. Хотя, конечно, в случае хоумпейджей — можно.

P>·>Напомню вопрос: А как ты будешь такой и-тест писать, если у тебя будет Time.Now? Что ассертить-то будешь? Как отличать два разных вызова Time.Now — клиентский от серверного?

P>·>Вот пришло тебе в ответе в неком поле значение "2024-01-23". как ты в и-тесте отличишь, что это вычислилось от вызова Time.Now на серверной стороне, а не на клиентской?
P>Смотрите пример про Колю Петрова, там ответ на этот вопрос
Смотрю, там нет ответа на этот вопрос. Код покажи.

P>>>Нам нужны именно фичи, кейсы, сценарии. В строчках кода это не измеряется. Смотрите, как это делают QA.

P>·>Как ты убедишься, что некая фича покрыта тестами? Или что все фичи и разные corner cases описаны в бизнес-требованиях? Покрытие хоть и ничего не гарантирует, но часто помогает обнаружить пробелы.
P>Я ж говорю — смотрите, как это у QA делается.
Может у вас идеальные QA, у нас они бегают к девам, спрашивая что тестировать. А часто вообще QA как отдельной роли нет. Как это делается у ваших QA?

P>>>- зарезервировать столик на трех человек на имя Коля Петров на следующую пятницу 21.00

P>·>Этот пример совершенно никак не отностися к обсуждаемому нами методу nextFriday. Или у тебя в твоём дизайне будет семь методов nextMonday...nextSunday?
P>Примером по Колю Петрова вам показываю, что такое интеграционные тесты. Вы же в них хотите проверять "а должно вот там вызываться!"
В том примере неясно как отличить откуда берётся информация. Что тест проходит и работает не означает, что функциональность работает правильно.

P>>>Остальных — от 1000 до 5000 и они занимают 99% времени.

P>·>Ну вот и говорю хоупейдж. По acc-тестам (селениум, сеть, многопоток, полный фарш) у нас было на порядок больше, выполнялось за 20 минут, правда на небольшом кластере.
P>Подозреваю, вы их просто подробили, "проверим, что время вон то"
Тесты вида: "клиент A через FIX: buy 100@1.23", "клиент B через web ui: sell 20@1.21", "assert: trade done B->A 20@1.22", "assert: dropcopy for trade report", "ждём до конца месяца", "assert: A получил statement по почте и там есть этот trade с ожидаемой датой" и т.п.

P>>>Не нравится метапрограммирование — можете придумать что угодно.

P>·>И что? Причём тут тесты-то?
P>Тесты где сравнивается выхлоп из базы не решают проблемы data complexity.
А я где-то заявлял обратное? И расскажи заодно какие же _тесты_ _решают_ проблемы data complexity [willy-wonka-meme.gif].

P>Отсюда ясно — что нужно добавлять нечто большее.

Причём тут тесты-то?

P>>>Вы там ногой читаете? Запрос не человек, сказать не может. А вот ревьюер может сказать "у нас такого индекса нет, тут будет фуллскан по таблице из десятков миллионов записей с выполнением регекспа и время работы на проде надо ждать десятки минут"

P>·>Или у вас неимоверно крутые ревьюверы, помнят наизусть сотни таблиц и тысячи индексов, или у вас хоумпейдж. Не все могут себе такое позволить.
P>Зато сразу ясно, где именно хоум пейдж, и почему ваши тесты работают ажно 20 минут.
И что тебе ясно? Тесты работают столько, потому что надо следить за временем работы тестов и стараться его минимизировать.

P>>>Это ж вы сунете тесты туда, где они не работают.

P>·>Это ты словоблудием занимаешься. Мы обсуждаем именно тесты, а ты тут в сторону доказательного программирования и мета уходишь.
P>Я вам о том, какие гарантии можно получить вашими тестами, а какие — нельзя.
P>В вашем случае вы ничего не гарантируете на тех данных, что у вас нет.
Причём тут _мои_ тесты??! Твои _тесты_ могут гарантировать что-то большее? С какого такого бодуна?

P>>>Как гарантировать — см пример номер 100500 чуть выше

P>·>Где чуть выше?
P>Я вам одно и то же пишу второй год.
Ну т.е. показать нечего.

P>>>У вас данных нет. Или вы щас скажете, что у вас уже есть все данные всех юзеров на будущее тысячелетие?

P>·>И как тобой предложенное "in: [fn1]" решает эту проблему? Или ты опять в сторону разговор уводишь?
P>Примерно так же, как assert.notnull(parameter) в начале метода или return assert.notEmpty(value) в конце
Не понял. Откуда у тебя возьмутся все данные всех юзеров на будущее тысячелетие из assert.notnull?

P>>>Наоборот.

P>·>Ваша цитата: Ага, тесты "как написано" Вам придется написать много больше, чем один тест — как минимум, пространство входов более-менее покрыть. Моками вы вспотеете это покрывать.
P>Ну ок, значит ничья в этом вопросе
Что мои тесты — тесты "как написано" — это твоя личная фантазия, основанная на твоём непонимании что тебе пишут. А вот что твои тесты — тесты "как написано", это видно по куску кода с pattern, который ты привёл.

P>>>Зачем вам собеседник, если у вас отличный дуэт с вашей телепатией?

P>·>В каком месте тут телепатия? Билд занимающий часы — это твои слова. И тесты в проде — тоже.
P>Вы всё знаете — и сложность проекта, и объемы кода, и начинку тестов, и частоту тех или иных тестов. Просто удивительно, как тонкая телепатия.
Так ты собственно всё рассказал. Перечитай что ты писал.

P>>>Такой тест гарантирует, что всё множество данных будет проходить именно через эту функцию.

P>·>Не гарантирует. В коде может быть написано "if(thursdayAfterRain())return {...in: [fn42]...}" и тест это не может обнаружить. Ещё раз — тесты ничего не могут гарантировать.
P>А я и говорю в который раз, что основное совсем не в тестах. В них мы всего лишь фиксируем некоторые свойства решения, что бы досталось потомкам в неискаженном виде.
А где у вас основное? И почему _основное_ не покрыто тестами??!

P>>>Я вам привел пример про Колю Петрова. Надеюсь, справитесь

P>·>Опять врёшь. Это не код.
P>Почему ж не код? Это в кукумбер переводится практически один к одному.
Ну вот давай значит клей показывай.

P>·>Я это и сказал. Если тут всё равно серверное время, то да, не функциональное, и тесты не пострадают. Тесты сломаются и должны сломаться если ты заменишь источник серверного времени на источник клиентского времени.

P>А вы точно интеграционные пишите? Чтото мне кажется, вы пишете высокоуровневые юнит-тесты.
Не буду о терминологии спорить. Не так уж важно как какие тесты классифицируются.

P>·>Я не понимаю какое отношение метапрограммирование имеет к бизнес-требованиям и к тому как писать тесты.

P>Такое же, как ваши классы, методы, dependency injection итд.
Всё смешалось, люди, кони.

P>>>Тесты дают гарантию, что пайплайн устроен нужным мне образом. А свойства результата, следовательно, будут определяться свойствами пайплайна. Тогда вам нужно сосредоточиться на выявлении этих свойств, а не писать одно и то же третий месяц кряду.

P>·>Тесты не могут давать гарантию.
P> Тесты именно ради гарантий и пишутся. Только чудес от них ждать не надо.
Гарантий чего? Они могут лишь проверить, что тестируемый сценарий работает. Что как где устроено — никаких гарантий.

P>>>Смотрите пример про Колю Петрова

P>·>Не нужен мне пример. Мне нужен код.
P>Бывает и хуже.
Как всегда бла-бла-бла, без конкретики.

P>>>Детали реализации нужно покрывать тестами, что бы первый залётный дятел не разрушил всю цивилизацию:

P>>>- оптимизации
P>·>Это перф-тесты.
P>Это слишком долго. Фейлы перформанса в большинстве случаев можно обнаруживать прямо во время юнит-тестирования.
Наивный юноша.

P>>>- трудноуловимые баги

P>>>- секюрити
P>>>- всякие другие -ility
P>>>- любой сложный код, где data complexity превышает ваше капасити
P>·>Это "за всё хорошее, против всего плохого". А секьюрити — это бизнес-требование и обязано быть покрыто фукнциональными тестами.
P>Важно, что бы явные фейлы обнаруживались гораздо раньше старта после деплоя. Тогда будет время подумать, как получить максимум сведений из самого деплоя.
После деплоя обнаруживаются фейлы в окружении, куда задеплоилось. Неясно как их обнаружить раньше. Это невозможно.

P>·>"Метапрограммирование тестируется" — зачем?

P>Тестировать нужно вообще всё, что вам взбредет в голову спроектировать. Тестируюя только крупные куски вы получаете дырявое покрытие
Мде.

P>>>Вы не просто утверждаете это, а идете дальше — утверждаете, что тесты прода это признак наличия багов, не той квалификации и тд.

P>·>Признак более позднего обнаружения багов.
P>Они показывают, выполнимы ли сценарии под нагрузкой(при изменениях сети, топологии, итд) или нет.
Для этого есть более адекватные подходы.

P>·>На это и придумали healthchecks (а конкретно liveness probe), проверка, что сервис — operational, явно проверяет себя и свои зависимости, что может отрабатывать трафик без фейлов ещё до того как пошли реальные запросы.

P>Каким чудом ваш пробы дают ответ, выполнимы или все ключевые сценарии при максимальной нагрузке? Сценарий — это та часть, про Колю Петрова, которая вам непонятна
А ты опять контекст потерял? Напомню: "Сертификат может обновиться не у вашего приложения, а у какого нибудь из внешних сервисов". Причём тут нагрузки? Причём тут Коля? Ты меня утомляешь. В следующий раз прежде чем написать очередную чушь, перечитай конткест. Мне уже порядком надоело твоё словоблудие.

P>>>Какой процесс вас застрахует от фейла на другой стороне которую вы не контролируте?

P>·>Большая тема. Начни отсюда: https://en.wikipedia.org/wiki/High_availability
P>Выполнимость сценариев это consistensy всех функций сервиса, а вовсе не high availability, как вы думаете.
КОНТЕКСТ!

P>·>Кодогенерация сама по себе медленный цикл разаработки не даёт. Любой кодогенератор работает от силы порядка минут.

P>Именно что сама по себе. Вам надо таскать тот манифест, сихнронизироваться с ним, следить, что бы все имели ту самую версию, тот самый генератор, итд итд
P>Во многих случаях этого можно избежать
Не осилили, ясно.

P>>>У нас кейс "переписать апи на rust" был 0 раз за 10 лет. Смена технологий от балды — тоже 0 раз. А у вас иначе, каждый месяц всё с нуля на rust переписываете?

P>·>C++, kotlin, java, groovy, scala (может ещё что забыл)... И таки да, спеки всё-таки есть в виде FIX или хотя бы avro/protobuf. А когда копадаются другие команды/внешине сервисы с подходом "нагенерим из аннотаций" — вечная головная боль интегрироваться с такими.
P>И ежу понятно — вам максимум с собой легко интегрироваться
Ну других по себе не судите, а мне вот сегодня опять приходится ворошить FIX спеку от Bloomberg, 891 страниц в PDF. Не, я работаю не там.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[51]: Что такое Dependency Rejection
От: Pauel Беларусь http://blogs.rsdn.org/ikemefula
Дата: 25.01.24 20:01
Оценка:
Здравствуйте, ·, Вы писали:

P>>Вы притягивает юзера к вашим тестам. Юзера можно притянуть только к функциональным тестам. Во всех остальных он не фигурирует.

·>Ну вроде о функциональных тестах тут и говорили, об проверках ожиданий пользователей. Ты опять как уж на сковородке по кочкам прыгаешь. Контекст не теряй, ок?

Вы лучше сами отмотайте, да посмотрите. Я вам про функции приложения, а вы мне про функции в джаве.

P>>Я вам подсветил, что бы полегче прочесть было

·>Ну если для тебя инлайн одной функции — это изменение дизайна приложения, то это твоё очень странное понимание слова "дизайн".

Это самое минимальное изменение дизайна, которое только может быть. Вы чего то заинлайнили — у кода появились новые свойства, например, возможность оптимизации

P>>1 получению размера внутреннего массива символов что есть неизменная величина

P>>2 гарантии иммутабельности соответсвующей ссылки. Т.е. никакой modify(str) не сможет легально изменить длину строки str
P>>3 джит компилятор в курсе 1 и 2 и инлайнит метод length минуя все слои абстракции, включая сам массив и его свойство

Похоже, тут вы торопились, и прочитать не успели

P>>Если вы запилите свой класс строк, нарушив или 1, или 2, компилятор ничего вам не сможет заинлайнить.

·>Бред, джит великолепно инлайнит length(), например, и у StringBuffer или даже LinkedList, и практически каждый getter/setter. И даже умеет инлайнить виртуальные функции.

Если вы вызовете str.length() сотню раз, джит сделает обращение к памяти ровно единожды — а дальше просто будет использовать значение как константу.
С мутабельной ссылкй или внутренним контейнером переменной длины так поступать нельзя
Похоже, что вы слишком торопитесь чего то настрочить

·>У тебя опять каша в голове. Напомню конткест: не провайдер времени, а nextFriday() вроде как ты собирался инлайнить.

·>Но вряд ли это всё заинлайнится, т.к. friday и работа с календарём — довольно сложный код, использующий таймзоны и календари, относительно.

У меня много чего инлайнится, и это даёт ощутимые бенефиты.

P>>Я вижу здесь другой кейс — вы так торопитесь, что обгоняете сами себя. Инлайнить методы нужно для оптимизации или изменения дизайна. В случае c Length инлайн в целях оптимизации выполнит компилятор.

·>Для оптимизации инлайнят только в в случаях если компилятор слишком тупой (что практически никогда) или случай ужасно нетривиальный (тогда и IDE не справится).

Конечная цель — оптимизация. Её можно достигнуть через изменение дизайна, например — нужные данные расположить рядом, убрать лишнее из hot path итд.

P>>Ну да — добавить компонент с конфигом и сайд-эффектом это стало вдруг нулевым изменением дизайна

·>Ну да. И? Как nextFriday() был, так и остался. Зачем менять дизайн?

Добавление такого компонента и есть изменение дизайна. Например, у вас теперь новый класс ошибок появился, которого раньше не было.

P>>Т.е. вы не в курсе, что значение функции закешировать гораздо проще, чем мастырить LRU кеш? Более того — такую кеширующую функцию еще и протестировать проще.

·>Ты издеваешься что-ли? Мы тут конкретный пример рассматриваем. Давай расскажи, как ты nextFriday(now) закешируешь "гораздо проще", где now — это текущее время.

Вы что, не в курсе мемоизации? Разница только в том, где связывание — или "где то там" или "прямо здесь".

P>>Вот-вот. Отдельный компонент. В том то и проблема. И вам все это придется мастырить и протаскивать. Только всегда "где то там"

·>Что всё? _Добавить_ новую логику в CalendarLogic, внутри него использовать этот кеш, и собственно всё. В чём _изменение_ дизайна-то? Можно обойтись одним новым приватным полем.

Вы описали это изменение дизайна. у вас с вашим кешем как минимум новые свойства у решения появились.

т проталкивать этот кеш и его настройки.
P>>Ровно так же как и у вас, только дизайн не надо перелопачивать.
·>У меня как-то так. Было
>
·>class CalendarLogic {
·>   private volatile LocalDate currentFriday;//вот наш кеш
·>   constructor... {
·>      var updater = () => currentFriday = doSomeComputations(instantSource.now());
·>      timer.schedule(updater, ... weekly);
·>      updater.run();
·>   }
·>   ...
·>   LocalDate nextFriday() {
·>      return currentFriday;
·>   }
·>}
·>

·>Т.е. меняется внутренняя реализация компонента и, скорее всего, надо будет заинжектить таймер внутрь CalendarLogic — дополнительный парам в конструктор.

Итого
1 — меняется внутренний дизайн компонента
2 — инжектится таймер
3 — теперь вам надо пройти по всем тестам где это аффектается и всунуть все что надо ради timer.schedule

В моем случае тесты nextFriday остаются неизменными, потому, что нет причины для изменения — кеш будет добавлен снаружи

P>>И где ваш код nextFriday и всех вариантов инжекции для разных кейсов — клиенское время, серверное, текущие, такое, сякое ?

·>А что там показывать? Вроде всё очевидно:
·>
·>var clientCalendarLogic = new CalendarLogic(timeProviders.clientClock());
·>var serverCalendarLogic = new CalendarLogic(timeProviders.systemClock());
·>var eventCalendarLogic = new CalendarLogic(timeProviders.eventTimestampClock());
·>


Вот видите — у вас ровно та же проблема, можно слегка ошибиться и clientClock уйдет в serverLogic.

P>>wiring и есть связывание. Код связывания в dependency injection, который вы называете wiring, и код связывания в контроллере — выполняют одно и то же — интеграцию. Разница отражает подходы к дизайну. И то и другое wiring, и то и другое связывание. И задача решается одна и та же. Только свойства решения разные.

·>Интересная терминология. Покажи хоть одну статью, где код в методах контроллеров называется wiring.

Если вы вызываете какую то функцию, это уже есть интеграция — то самое связывание. В моем случае я помещаю связывания в контроллер, часть — в юз кейс, см у фаулера 'src/restaurantRatings/topRated.ts'

P>>Похоже, что пример для Буравчика вы не поняли

·>Код я понял. Я не понял зачем такой код и что именно он улучшил, я предполагаю этого никто тут не понял. А про ЧПФ и классы было в обсуждаемой тут в начале топика статье.

Вводится изоляция — вместо низкоуровневой зависимости на репозиторий появляется абстракция. Ровно то же делает фаулер, 'src/restaurantRatings/topRated.ts'

P>>Вы же рассказываете, что проблему увидите на старте приложения. Прод у вас получает конфиг прода и другие вещи прода. Вот на старте и узнаете, что у вас не так пошло. А надо зафейлить тест, когда вы только-только накидали первую версию контроллера — в этот момент у вас буквально приложения еще нет

·>Чтобы зафейлить тест, код надо стартовать. Не всё приложение, ясен пень, зачем для теста конфиг прода, ты в своём уме?!

Если зафейлить можно в юнит-тестах, то это всё равно быстрее вашего случая.

·>У меня аналогом будет

·>
·>var repo = mock(Repo.class);
·>var vancouverRestaurants = List.of(
·>  new Restaurant("cafegloucesterid", "Cafe Gloucester"),
·>  new Restaurant("baravignonid", "Bar Avignon"),
·>);
·>when(repo.getTopRestaurants("vancouverbc")).thenReturn(vancouverRestaurants);
·>var ratingsHandler new TopRatedHandler(repo);
·>

·>Теперь чуешь о чём я говорю что кода меньше?

Я вижу, что вы сравниваете код с фремворков на моках с кодом без такого фремворка. Вот, убрал лишнее, сравнивайте

·>
·>    const vancouverRestaurants = [
·>      new Restaurant( "cafegloucesterid", "Cafe Gloucester"),
·>      new Restaurant( "baravignonid", "Bar Avignon")
·>    ];
·>    const dependenciesStub = mock({}).whenCalledWith('getTopRestaurants', 'ляляля').returns(restaurants)
·>    const ratingsHandler: Handler =
·>      controller.createTopRatedHandler(dependenciesStub);
·>


Похоже, вы не вдупляете ни пример Фаулера, ни мой пример для Буравчика.

·>И не можете проверить что зависимости — operational? Пинг послать?

·>А для проверок consistency — нужно версирование и проверка совместимости версий. Гонять тесты — не _нужно_. Хотя, конечно, в случае хоумпейджей — можно.

Ну вот сценарий
— юзер залогинился
— добавил платежную карту
— набрал покупок
— нажал кнопку оплатить

Как ваше версионирование расскажет, что под нагрузкой у пользователя оплата не проходит с сообщением "карточка не добавлена" а список карточек вдруг оказывается пуст?
Подробнее, пожалуйста, как такую проблему решит версионирование. Для простоты условимся, что у вас нет внешних зависимостей.

P>>Смотрите пример про Колю Петрова, там ответ на этот вопрос

·>Смотрю, там нет ответа на этот вопрос. Код покажи.

Вы не знаете, что такое кукумбер? Забавно

P>>Я ж говорю — смотрите, как это у QA делается.

·>Может у вас идеальные QA, у нас они бегают к девам, спрашивая что тестировать. А часто вообще QA как отдельной роли нет. Как это делается у ваших QA?

traceability matrix

P>>Примером по Колю Петрова вам показываю, что такое интеграционные тесты. Вы же в них хотите проверять "а должно вот там вызываться!"

·>В том примере неясно как отличить откуда берётся информация. Что тест проходит и работает не означает, что функциональность работает правильно.

Вот-вот.

P>>Тесты где сравнивается выхлоп из базы не решают проблемы data complexity.

·>А я где-то заявлял обратное? И расскажи заодно какие же _тесты_ _решают_ проблемы data complexity [willy-wonka-meme.gif].

Я же вам много раз сказал. Вы утверждаете, что где то показали здесь решение, и несколько раз сослались на свои примитивные тесты. Отсюда следует, что вы настаиваете на тестах.

P>>Отсюда ясно — что нужно добавлять нечто большее.

·>Причём тут тесты-то?

Вы же который раз ссылаетесь на ваши примитивные тесты именно в контексте data complexity

P>>Зато сразу ясно, где именно хоум пейдж, и почему ваши тесты работают ажно 20 минут.

·>И что тебе ясно? Тесты работают столько, потому что надо следить за временем работы тестов и стараться его минимизировать.

А посоны то и не знают... @

P>>В вашем случае вы ничего не гарантируете на тех данных, что у вас нет.

·>Причём тут _мои_ тесты??! Твои _тесты_ могут гарантировать что-то большее? С какого такого бодуна?

У меня два разных набора тестов, каждый из который решает свою часть проблемы. У вас — один.

P>>Примерно так же, как assert.notnull(parameter) в начале метода или return assert.notEmpty(value) в конце

·>Не понял. Откуда у тебя возьмутся все данные всех юзеров на будущее тысячелетие из assert.notnull?

Этот assert сработает для всех вызовов функции, сколько бы вы туда ни пихали, хоть тысячу лет кряду, хоть миллион.
Вы, похоже, не в курсе, как инварианты, пред-, и пост-условия используются.

P>>·>В каком месте тут телепатия? Билд занимающий часы — это твои слова. И тесты в проде — тоже.

P>>Вы всё знаете — и сложность проекта, и объемы кода, и начинку тестов, и частоту тех или иных тестов. Просто удивительно, как тонкая телепатия.
·>Так ты собственно всё рассказал. Перечитай что ты писал.

Я писал что много юнит-тестов, но не писал сколько из них чекают запросы к бд. А вы понаделывали выводов без этой информации — телепатия в чистом виде.

P>>А я и говорю в который раз, что основное совсем не в тестах. В них мы всего лишь фиксируем некоторые свойства решения, что бы досталось потомкам в неискаженном виде.

·>А где у вас основное? И почему _основное_ не покрыто тестами??!

Потому, что основная работа по обеспечению качества это совсем не тесты. Тесты это исключительно вспомогательный инструмент — фиксируем некоторые ожидания, свойства решения.
Но само решение нужно иметь до того, как начнете покрывать его тестами
Например — вы можете заложиться на стабильную структуру пайплайна. В этом случае и тестами надо обеспечить именно это — стабильность пайплайна.
А можете на конкретные наборы данных — тогда вам нужно чудовищное количество данных.
А можно взять и то, и другое, добавить код-ревью, ассерты, логирование и получите куда больше, чем одними вашими тестами на провеку выхлопа из базы

P>>Почему ж не код? Это в кукумбер переводит ися практически один к одному.

·>Ну вот давай значит клей показывай.

Какой еще клей?

P>>·>Я это и сказал. Если тут всё равно серверное время, то да, не функциональное, и тесты не пострадают. Тесты сломаются и должны сломаться если ты заменишь источник серверного времени на источник клиентского времени.

P>>А вы точно интеграционные пишите? Чтото мне кажется, вы пишете высокоуровневые юнит-тесты.
·>Не буду о терминологии спорить. Не так уж важно как какие тесты классифицируются.

Очень даже важно — если вы в тестах проверяете "вон тот источник не используется" то это к интеграционным имеет крайне слабое отношение

P>>Такое же, как ваши классы, методы, dependency injection итд.

·>Всё смешалось, люди, кони.

Что вам непонятно? Одна техника в программировании и другоя техника в программировании, обеими можно решить задачу.

P>>·>Тесты не могут давать гарантию.

P>> Тесты именно ради гарантий и пишутся. Только чудес от них ждать не надо.
·>Гарантий чего? Они могут лишь проверить, что тестируемый сценарий работает. Что как где устроено — никаких гарантий.

Вы сейчас плавно отказываетесь от своих слов "Тесты не могут давать гарантию"

P>>Это слишком долго. Фейлы перформанса в большинстве случаев можно обнаруживать прямо во время юнит-тестирования.

·>Наивный юноша.

Нисколько. Вероятно, вы не догадываетесь, что где то может быть другая область, с другой спецификой.

P>>Важно, что бы явные фейлы обнаруживались гораздо раньше старта после деплоя. Тогда будет время подумать, как получить максимум сведений из самого деплоя.

·>После деплоя обнаруживаются фейлы в окружении, куда задеплоилось. Неясно как их обнаружить раньше. Это невозможно.

Деплой дает вам новое состояние системы, которого раньше не было.

P>>Тестировать нужно вообще всё, что вам взбредет в голову спроектировать. Тестируюя только крупные куски вы получаете дырявое покрытие

·>Мде.

Именно. Вопрос в том, умеете ли вы сделать это дешовым в тестах или нет.

P>>Они показывают, выполнимы ли сценарии под нагрузкой(при изменениях сети, топологии, итд) или нет.

·>Для этого есть более адекватные подходы.

Вы пока ни одного ни привели.

P>>Каким чудом ваш пробы дают ответ, выполнимы или все ключевые сценарии при максимальной нагрузке? Сценарий — это та часть, про Колю Петрова, которая вам непонятна

·>А ты опять контекст потерял? Напомню: "Сертификат может обновиться не у вашего приложения, а у какого нибудь из внешних сервисов". Причём тут нагрузки? Причём тут Коля?

Проблема с сертификатом может повалить выполнение сценариев. Нагрузка может точно так же повалить выполнение сценария. А ценарий один — см Коля Петров.

P>>И ежу понятно — вам максимум с собой легко интегрироваться

·>Ну других по себе не судите, а мне вот сегодня опять приходится ворошить FIX спеку от Bloomberg, 891 страниц в PDF. Не, я работаю не там.

Как думаете, сколько % от общей массы программистов заняты тем же?
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.