Здравствуйте, Pauel, Вы писали:
P>>>Чем ваши acceptance и smoke отличаются от e2e ? P>·>acceptance и smoke — подвиды e2e. Первые запускаются до деплоя, вторые запускаются сразу после. Если smoke-тест падает — делается откат к предыдущей версии. P>Итого — e2e у вас есть.
Есть. Но 99% этих тестов — с моками, которые ты так ненавидишь. Без моков только smoke — коротый тестирует этот самый 1% (по самым оптимистичным оценкам, скорее всего даже сильно меньше).
P>>>·>У тебя память как у рыбки. Цитирую: P>>>·>
P>>>·>·>потенциально может быть null — ты не дождёшься окончания.
P>>>·>
P>>>Я шота не понял ваш аргумент. P>·>В смысле ты не понял что ты хотел сказать своей цитатой? Ничем помочь не могу. P>Это ваша цитата http://rsdn.org/forum/design/8701337.1
Я уже совершенно перестал поспевать за полётом твоей мысли. Ты на эту мою цитату ответил что-то уже. Видимо тогда ты понял, а сейчс уже не понял? Перечитай ещё раз. Если что неясно, задавай вопросы.
P>Вот мы и узнали, у кого память как у рыбки. И уже в который раз
Угу, наконец-то и ты себя узнал.
P>·>Аргумент чего? Это твоя цитата. Что она значит — разбирайся сам. P>"а что если в колонке пусто" — вот это вроде бы уже выясняли. Вам еще хочется пройтись по кругу?
Выяснили, что твои тесты бесполезны, да.
P>>>Это значит, что у вас простой кейс — для всего дадена спека. Что делать будете, кода результат вашей работы это и спека, и софт по ней? P>·>Ещё проще. P>Нет, не проще. В этом случае вам нужно отрабатывать не один вариант, который навязывается спекой, а несколько, и выбирать из них оптимальный для предполагаемых потребителей. P>Накидали вы один, сделали демо — а вам сказали, это не годится. Вы другой вариант — и тот забраковали. И так до тех пор, пока не выдадите более-менее внятный. А походу дела подтягиваете спецификацию
Это проще, т.к. мы контролируем и спеку, и реализацию.
P>>>Вы зачем то снова пустились в фантазирование. Юзер у вас что, на бакенде сидит? Нет ведь. Он в каком то клиентском приложении. Вот там и ищите фоновый процессинг. P>·>В смысле фоновой процессинг в 3rd party внешней системе? Ну тогда это их проблема, поведение нашего api задокументировано. P>Т.е. вы не понимаете, что фронтенд, мобайл, десктоп может быть частью вашей системы?
Тогда не понимаю проблему. В нашем клиенте тоже источник времени который можно мочить, а не дух святой.
P>>>В проде указали 30 минут, а из за мержа конфигов у вас стало 300, например, потому что это дефолтное значение. Это снова пример проблемы которая идет мимо ваших моков. P>·>Дифф конфигов прода и теста покажет отстутствие параметра. P>Как то слишком категорично. Думаете вы один умеете конфиги сравнивать? P>Посмотрите в коде — где у вас дефолтные значения для параметров по умолчанию?
Ээээ... В коде.
P>У вас что, все 100500 возможных констант исключительно в конфигах? P>Дефолтные значения могут подкидываться даже либами, если вы забудете подкинуть значение.
Не понял. Когда прогоняешь acceptance тесты, ты прогоняешь тесты для системы версии <длинный sha>. Ровно этот же sha и деплоится на прод и гарантированно имеет все те же либы и код, с точностью до бита. Следовательно и константы имеют идентичное значение. В эпоху докер-имаджей иметь такие проблемы как у вас — должно быть просто стыдно.
P>>>Именно что в мониторинге, а не в ваших тестах на моках. И только под нагрузкой. P>·>Угу, и? P>Мы только что выяснили, что моками вы никаких гарантий дать не можете — все равно придется ждать деплоя на прод и смотреть в мониторинг когда нагрузка стала той самой
Зачем? Нагрузочное тестирование можно и в отдельном перф-енве сделать. Путём ускоренного проигрывания событий из вчерашнего прода.
P>>>Вы до сих пор название топика не вкурили? dependency rejection это целиком про дизайн. P>·>Это вроде давно обсудили. Ты потом переключился на моки. P>Это все в контексте дизайна который определяется тестами. Написали под моки — значит пропихивать зависимости так, а не иначе. И всё, приплыли.
Ты так говоришь, будто это что-то плохое.
P>>>Построили дизайн под моки — естественно, кроме моков ничего и не будет. P>·>Нет никакого дизайна под моки. Есть внешние системы — будут моки. P>У вас именно так — вы считаете моки единственным инструментом для работы с зависимостями.
Других ты не смог предложить.
P>·>Мде. Видимо вообще вы с Sinclair не вдупляете что такое глобальные переменные. Поэтому и используете, не видя разницы. P>Скорее это вы почему то не понимаете, что мир не сводится к вашему проекту.
Так вы сами себе в ногу стреляете своим DateTime.Now().
P>Подозреваю, вы и под глобальными переменными вы подразумеваете чтото другое.
А что я должен подразумевать по-твоему?
P>>>Да, вы эту сказку много раз повторяли — у вас мониторинг любую даже гипотетически возможную проблему обнаруживает сразу. P>·>Это ты опять врёшь, я такое не говорил. А у вас тестинг занимается обнаружением любых гипотетических проблем, да? P>Очевидно, что нет. Только зафиксированых на момент написания теста. И этих проблем он обнаруживает на два порядка больше мониторинга.
А разница в том, что мы, благодаря мокам и дизайну, можем обнаружить все эти зафиксированные проблемы ещё до планирования релиза, а вы — только после как "Юзеры репортают проблемы". Зато всё по фаулеру!
P>>>Вы что, повсюду в бизнес-логике пишете "if user.isTest() " ? Очень вряд ли. Интеграция для тестового юзера и реального будет та же самая. Соответственно, обнаруженные проблемы практически наверняка будут играть и для тестовых юзеров. P>·>Т.е. в конце месяца ты узнаешь, что у вас сломано создание statement? Это уже очень поздно. Ты получишь злобное письмо как минимум от каждого десятого юзера. P>Вы снова занялись фантазированием. Вот проверили вы список проблем со значением в конфиге, условно, 5 минут. P>А потом на проде вылезла новая проблема ровнёхонько через месяц P>- например, задачи на создание стейтмента создаются, но висят ибо очередь забита чего у вас в тестах не было из за недостаточной загрузки
Забитость очереди, ещё раз, тривиально детектится мониторингом.
P>Все что вы можете P>1. покрыть тестами всех уровней P>2. проверять регулярно на проде, что бы обнаружить проблему до того, как напорются пользователи
Я не понял как ты предлагаешь обнаружить это. Создание statement происходит для всех в конце месяца, что для юзеров, что для твоих тестов. Т.е. твой тест если и упадёт, то в лучшем случае ровно в тот же момент, что и у юзеров начнутся проблемы. Это уже слишком поздно.
P>>>Мы уже выясняли — вы держите только ту часть acceptance, которую можно выполнить за полчаса. А другие на идут дальше этого 1% P>·>Нет, мы дизайним так, чтобы любой acceptance можно было выполнить за пол часа, даже для сценариев которые требуют sleep(1 month). Время-то мочёное. P>Значит это никакой не acceptance если вы мокаете время. Вероятно, это ваша местная специфика.
С чего это? Acceptance это не про моки, а про good to go.
P>Повторяюсь уже P>
P>new controller(параметры)
P>
P>Отдельные части это технически вообще как юнит-тесты.
Т.е. поднимается вся инфра? Ведь контроллер может использовать ВСЁ.
Что за "параметры"? Моки, ведь, да?
P>>>Что вам непонятно было в прошлый раз? Вы продолжаете задавать один и тот же вопрос, получаете один и тот же ответ. Чего вы ждете? P>·>Что вам непонято было в прошлый раз? Вы продолжаете гнать пургу про запуск тестов в проде случайным образом в случайные моменты времени. P>Прогон тестов на проде это не моя придумка. Уже лет десять назад было довольно популярной практикой.
Где? Есть статьи?
P>>>Вы же сами сказали — у вас ажно 1% покрыт. Вот на остальные 99% и нужно придумать чтото получше чем есть у вас. P>·>smoke-тестом — да, ~1%. А больше и не надо. P>А acceptance сколько покрывает?
Почти всё. Но с моками.
P>>>Теорема Райса с вами не согласна. Нету у вас 100% надежного способа гарантировать даже true из boolean. P>·>Верно, т.е. эти ваши тесты ничего нового не дадут. P>мой подход сродни статической типизации — на него теорема Райса не действует
P>·>Я же объяснил, для управления этим используются другие средства вместо прогона тестов. P>Вы эти другие пока не показали. Вам удобнее обсуждать тесты отдельно от дизайна.
Создавай новый топик, задавай вопросы.
P>>>Вашу интеграцию тестируют acceptance и smoke. я вот не удивлюсь, если окажется что ваши acсeptance это ровно то же, что и e2e P>·>Зависит от терминологии. Acceptance работают с моками внешних систем. Это считается как e2e? P>Условно, да
Ну ок. Т.е. мы тестируем с моками, без прода. Всё ок.
P>>>А также те, для которых тесты существуют, но причин более одной, разница прода и стейджа итд. P>·>Разница в чём? У бинарников полное совпадение до бита. Отличаться могут только конфиги, и это контролируется diff-ом. P>Нагрузка, данные, энвайрмент, время, сеть — отсюда и лезет недетерминизм.
Нагрузка и данные недетерминизм не создают.
Энвайрмент, время — он у вас лезет, потому что вы сами его создаёте этим же своим DateTime.Now() и бесконтрольными конфигами.
Сеть — это по сути многопоточка. Самое сложное, конечно. Но тестами оно вообще никакими не лечится, только общим подходом.
P>·>Если только недетерминизм есть какой-то, из-за ошибок в многопоточке, например. Но такое тестами и не найти, тем более на проде. Если только случайно повезёт. P>Именно для этого некоторые конторы в момент максимальной нагрузки запускают тесты прода. Это не шутка. P>Таким образом тестируется система в нагрузке.
Жуть.
P>>>А еще он поможет подсветиться лампочке в мониторинга до того, как юзер напорется на проблему, а не после P>·>Это как? Вызвав нехватку ресурсов? P>Например, потому что запускается в момент большой загрузки.
Это гораздо проще и с более надёжным результатом вызвать в тестовом окружении.
P>>>Комбинаций у вас нет. Так? P>·>Комбинации есть ровно те, что что и у вас. P>Так и у меня их нет. И мой подход основан на пост-условиях. Это почти что статическая типизация, с некоторым упрощением.
Ага-ага Почти беременна, с некоторым упрощением.
P>А вот тесты здесь играют совсем другую роль — они фиксируют наличие пост-условия.
Не фиксируют, наивый юноша.
P>>>Структурная эквивалентность, синтаксис теореме Райса не подчиняются. P>·>Это и означает, что ваш тест проверяет, что works as coded. Абсолютно бесполезное занятие. P>work as coded это если копировать sql из приложения в тесты.
Так ты не стесняйся, покажи свой пример кода для которого ты показал свой тест с pattern. Получится ровно копипаст.
P>А если в приложении билдер, а в тестах — его выхлоп на конкретных параметрах, то мы напрямую проверяем маппер — о большинстве проблем вы узнаете еще до запуска на реальной базе данных
Именно, что у тебя ровно то же, что и у меня: "на конкретных параметрах". ЧТД. Статическая типизация, май год! А как дышал...
P>>>А вот ваши тесты это чистой воды семантические свойства которые полностью подчиняются теореме Райса P>·>Так тесты и должны проверять семантику. Не гарантировать корректность семантики, а проверять для конкретных примеров. Тесты делают не с целью верификации свойств кода, а с целью упрощения написания кода, как исполнимая документация. P>Ну так конкретных примеров то нет. А раз так — то используем "1 типизация, пост-условия, инварианты, пред-условия, структурная эквивалентность"
Строкой выше было "на конкретных параметрах", куда делось?!
P>>>Ваши тесты ничего не дают без знания конкретных комбинаций на которых можно отработать тест P>·>Ваши тоже. P>Вы пока не смогли пример адекватный привести, который сломает хотя бы примитивный limit 10.
Привёл в ответе Sinclair с ?:-оператором.
P>>>У Буравчика как раз тот случай, где нужны моки. Я же сразу это сказал, а вы третий месяц срываете покровы. P>·>Мне ещё не довелось увидеть случая где не нужны моки. И ты правду скрыавешь. P>Потому, что вы изначально думаете в единицах моков, и почти всё к ним и сводите.
Ага, да ты телепат — знаешь что я думаю. А показать-то есть что?
P>>>В том то и дело, что не даёт. У вас нет комбинаций, а потому проверяние количества записей смысла не имеет — в тестовой базе просто нет тех самых данных. P>·>У меня ровно те же комбинации, что будут и в твоих тестах. Отличается только часть в ассертах. Вместо проверки синтаксиса — sql-код запускается и валидируется интересный с тз бизнеса результат. P>Комбинации в бд не показывают проблему. Следовательно, вы вытащите её на прод.
Если у меня не показывают, то и у тебя не показывают. Об чём спор-то?
P>>>В прод коде только билдер. Откуда копипаста взялась? P>·>В прод-коде у тебя будет реализация, "LIMIT 10", в тест-коде у тебя будет .eq("...LIMIT 10"). P>. P>Не будет. Нигде в коде приложения нет явного LIMIT 10. Лимит вычисляется билдером. Будет ли это LIMIT или подзапрос с нужным выражением — дело десятое. P>Важно, что лимит будет обязательно. И мне не надо перебирать всю таблицу истинности билдера на интеграционных тестах.
Главное верить, и свечку поставить.
P>>>Это всё из другого проекта, я им больше не занимаюсь P>·>Ну сделай набросок, чтобы понятно было. P>Все что мог, я вам показал. Что еще нужно? Запрос вы видите. Он собирается из разных частей. Каждая тестируется по отдельности. P>Главное что бы структура была корректной.
Чтобы был конкретный код, в который можно пальчиком тыкнуть. Иначе это твоё словоблудие уже надоело.
P>>>А вот у пост-условия такого ограничения нет в принципе. P>>>Идея понятна? P>·>Понятна конечно, я это тебе и говорил. Вот только постусловие ты проверить не сможешь тестом. P>Тестом я не проверяю пост-условие. Тестом я проверяю, что оно на месте.
Не проверяешь, Райс мешает.
P>>>Известно какое — в память не влазит P>·>Что не влазит? Кому не влазит? Нихрена не понял. P>Результат запроса не влазит в память.
Причём тут isOdd?
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали: ·>Это разница между хрупкими тестами, которые тестируют детали реализации и полезными тестами, которые тестируют аспекты поведения.
"Аспекты поведения" можно зафиксировать e2e-тестами только в исчезающе малом количестве случаев.
·>Как проверить что к _любому_ запросу. В твоём _тесте_ проверяется только для одного конкртетного запроса с одним из заданных в тесте criteria.
Все остальные варианты исключаются дизайном функции построения запроса.
·>Из привёдённых снипппетов кода я вижу что именно библотечный и по-другому вряд ли может быть, т.к. код получится совсем другим.
Ну ок, минус один тест.
·>Да банально скобочки забыты вокруг ?: — ошиблись в приоритете операций, должно было быть (someMagicalCondition(criteria) ? buildOptimizedQuery(criteria) : buildWhere(criteria)).addLimit(pageSize).
Повторюсь: внесение скобочек и ветвлений в текст функции buildQuery запрещено. Все вот эти вот фантазии про buildOptimizedQuery нужно вносить внутрь buildWhere.
Запрет реализуется орг.мерами — от обучения разрабочиков, до код ревью и отбора прав на соответствующую ветку репозитория.
Как вариант (если у нас достаточно развитая платформа) — сигнатурой функции, которая обязуется возвращать ILimitedQueryable<...>.
·>Ведь проблема в том, что мы не описали в виде теста ожидаемое поведение. А тестирование синтаксиса никакой _новой_ информации не даёт, синтаксис и так виден явно в диффе.
·>В этом и суть что с т.з. _кода теста_ — проверять наличие addLimit или size()==10 — даёт ровно те же гарантии корректности лимитирования числа записей. Но у меня дополнительно к этому ещё некоторые вещи проверяются и нет завязки на конкретную реализацию, что делает тест менее хрупким.
У вас есть завязка на конкретное наполнение тестовых данных. И это делает код ваших тестов значительно более медленным, и примерно настолько же хрупким.
Вот у вас там кто-то подпилил код репозитория, и теперь применяются чуть более строгие критерии; параллельно в одной из комбинаций вы забыли добавить лимит. Тест по-прежнему зелёный, т.к. вернулось столько же записей, вот только на данных прода там поедет половина базы.
Вы, если я правильно понял, предлагаете это предотвращать просто просмотром синтаксиса при диффе. Ну так тогда все ваши аргументы начинают работать против вас.
·>Верно. Т.е. оба подхода при прочих равных этот аспект проверяют ровно так же. Но твой тест покрывает меньше аспектов.
Ну так это сделано намеренно. Потому что те "дополнительные аспекты", которые тщится проверять ваш тест, не нужно проверять в каждом из юнит-тестов.
Если, скажем, вы боитесь напороть в критериях фильтров, перепутав != и == (всякое бывает), то да, можно проверить этот аспект.
Но тут же идея как раз в том, что stateless подход позволяет нам распилить сложный запрос на несколько фрагментов, и каждый протестировать по отдельности.
В вашем подходе ничего подобного не получится — есть чорный ящик репозитория, в который мы кидаем какие-то параметры, и он нам что-то возвращает. Из того, что для параметров A и для параметров B возвращаются корректные результаты, никак не следует то, что корректными будут результаты для комбинации A & B. В моей практике косяки склеивания частей запроса встречались гораздо чаще косяков перепутывания < и >.
А вопросы строгости/нестрогости неравенств упираются не столько в навыки программиста, сколько в интерпретацию требований заказчика. Ну, там, когда мы запрашиваем отчёт с 03.03.2024 по 03.03.2024 — должны ли в него попасть записи от 03.03.2024 0:00:00.000? Если да, то должна ли в него попасть запись 03.03.2024 12:30:05? Это — работа аналитика, который и будет писать в спеку конкретные < end, <= end, или там < end+1.
Неспособность разработчика скопипастить выражение из спеки, да ещё и дважды подряд (в предположении, что тесты пишет тот же автор, что и основной код) — это какая то маловероятная ситуация.
·>Причём тут вредители? Я очень часто путаю == и != или < и > в коде. Поэтому ещё один способ выразить намерение кода в виде тестового примера с конкретным значением помогает поймать такие опечатки ещё до коммита. Может ты способен в сотне строк обнаружить неверный оператор или забытые строки приоритета, но я на такое не тяну.
Не, я тоже не тяну. Поэтому не надо писать код на сотню строк, где перемешаны операции склейки запроса и написания предикатов.
Надо есть слона по частям. В одиночном предикате, где есть ровно одно сравнение, вы вряд ли допустите такую опечатку.
А если вы пишете всё простынкой SQL — ну, удачи, чо. Про такой код можно только молиться. Я в своей карьере такого встречал (в основном до 2010), и во всех тех местах были недотестированные ошибки.
·>Поэтому мне надо это дело запустить и просмотреть на результат для конкретного примера.
·>Так ведь ревью делается и для тестов, и для кода. Мне по коду теста сразу видно, что find(10).size() == 10 — правильно. А вот уверено решить что должно быть "rownum > 10", "rownum <= 10" или "rownum < 10" — лично я не смогу, тем более как ревьювер большого PR.
Всё правильно, поэтому нефиг писать rownum <=10. Надо писать .addLimit(10). Его 1 (один) раз тестируют, и потом тысячи раз пользуются, полагаясь на его корректность.
А вы сначала создали себе трудности, использовав неудачную архитектуру, а потом пытаетесь их преодолеть при помощи медленных и неполных тестов.
·>Какую сложную функцию? Вы просто переписали кривой код на прямой. Тесты тут причём?
Сложная функция, которая превращает Criteria в набор записей. У вас это даже не функция, а объект — потому что у него есть внешние stateful dependencies.
А у нас это функция, которая состоит из цепочки простых функций, каждая из которых легко тестируется.
В вашем подходе так сделать невозможно — вы не можете взять два метода из репозитория, скажем findUsersByMailDomain() и findUsersByGeo(), и получить из них метод findUsersByMailDomainAndGeo().
Именно в силу выбранной архитектуры.
·>Я такого не предлагаю.
Да ладно!
·>Да пожалуйста, если получится.
Я не вижу причин, по которым это не получится. Вы же даже стремиться к этому отказываетесь, потому что пребываете в иллюзии невозможности
·>Код и тесты обычно пишет один человек. Или вы парное программирование используете?
Нет. Обычно используется всё же разделение между QA и Dev.
·>Если код функции котороткий, то значит у тебя такхи функций овердофига. И в каждой из паре операций можно сделать очепятку.
Поскольку отлаживаются и тестируются эти функции по отдельности, шансов пропустить опечатку гораздо ниже, чем когда у нас один метод на пятьсот строк, с нетривиальным CFG и императивными stateful зависимостями.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>·>Это разница между хрупкими тестами, которые тестируют детали реализации и полезными тестами, которые тестируют аспекты поведения. S>"Аспекты поведения" можно зафиксировать e2e-тестами только в исчезающе малом количестве случаев.
Почему? Ты, наверное, под "зафиксировать" что-то суровое понимаешь.
S>·>Как проверить что к _любому_ запросу. В твоём _тесте_ проверяется только для одного конкртетного запроса с одним из заданных в тесте criteria. S>Все остальные варианты исключаются дизайном функции построения запроса.
ИМЕННО! Твой тест тестирует только один небольшой сценарий для небольшого частного случая. Как и мой. И никаких универсальных гарантий или проверок того что "пол базы не загружается" он давать не может в принципе. Это невозможно обеспечить тестами. Никакими, т.к. остальных вариантов будет примерно ∞ — 1.
S>·>Да банально скобочки забыты вокруг ?: — ошиблись в приоритете операций, должно было быть (someMagicalCondition(criteria) ? buildOptimizedQuery(criteria) : buildWhere(criteria)).addLimit(pageSize). S>Повторюсь: внесение скобочек и ветвлений в текст функции buildQuery запрещено. Все вот эти вот фантазии про buildOptimizedQuery нужно вносить внутрь buildWhere. S>Запрет реализуется орг.мерами — от обучения разрабочиков, до код ревью и отбора прав на соответствующую ветку репозитория.
ИМЕННО!! Тесты-то тут не причём. А следовательно, отнюдь, мой тест Такое Же Быдло™ как и Ваш.
Однако, мой тест менее хрупкий и покрывает немного больше, т.к. выражает намерение напрямую.
S>Как вариант (если у нас достаточно развитая платформа) — сигнатурой функции, которая обязуется возвращать ILimitedQueryable<...>.
Если бы у бабушки были бы... Но давай-ка из страны розовых пони возвращайся.
S>·>В этом и суть что с т.з. _кода теста_ — проверять наличие addLimit или size()==10 — даёт ровно те же гарантии корректности лимитирования числа записей. Но у меня дополнительно к этому ещё некоторые вещи проверяются и нет завязки на конкретную реализацию, что делает тест менее хрупким. S>У вас есть завязка на конкретное наполнение тестовых данных. И это делает код ваших тестов значительно более медленным, и примерно настолько же хрупким.
Наполнением теста занимается окружение самого теста и аккуратно подготавливается и не зависит от реализации. Хрупкость-то откуда? Тестовые данные должны соответствовать бизнес-требованиям. А хрупкость — это о деталях реализации. Если тест проходил, то он должен проходить в любом случае, вне зависимо от реализации, если требования не меняются; и наоборот, тест должен падать, если требования меняются — это и есть прочный тест.
Насчёт медленности — не совсем правда, нынче субд тысячи запросов тянут в секунду, так что за несколько минут ты можешь тысячи тестов прогнать без проблем. Ведь надо яблоки с яблоками сравнивать. А наличие супербыстрых ютестов тебе не даст права не тестировать с базой.
S>Вот у вас там кто-то подпилил код репозитория, и теперь применяются чуть более строгие критерии; параллельно в одной из комбинаций вы забыли добавить лимит. S>Тест по-прежнему зелёный, т.к. вернулось столько же записей, вот только на данных прода там поедет половина базы.
Это ровно то же грозит и вам. Т.к. у тебя "проверяется только для одного конкртетного запроса с одним из заданных в тесте criteria", с чем ты согласился.
S>Вы, если я правильно понял, предлагаете это предотвращать просто просмотром синтаксиса при диффе. Ну так тогда все ваши аргументы начинают работать против вас.
Так это вы и предлагаете: "Запрет реализуется орг.мерами — от обучения разрабочиков, до код ревью и отбора прав на соответствующую ветку репозитория".
S>·>Верно. Т.е. оба подхода при прочих равных этот аспект проверяют ровно так же. Но твой тест покрывает меньше аспектов. S>Ну так это сделано намеренно. Потому что те "дополнительные аспекты", которые тщится проверять ваш тест, не нужно проверять в каждом из юнит-тестов.
Т.е. всё то же и у меня, и у вас.
S>Если, скажем, вы боитесь напороть в критериях фильтров, перепутав != и == (всякое бывает), то да, можно проверить этот аспект.
Каким образом проверить-то? Я предлагаю — в виде явного тупого конкретного примера "save(vasya); assert find(petya).isEmpty()" — вот такой тест тупой и очевидный, даже дебилу понятно что он правильный — как раз по мне. А вот тест который проверит наличие предиката конкретно ==, а не != — я не вытяну. Вдруг там где-нибудь двойное отрицание закрадётся...
S>Но тут же идея как раз в том, что stateless подход позволяет нам распилить сложный запрос на несколько фрагментов, и каждый протестировать по отдельности. S>В вашем подходе ничего подобного не получится — есть чорный ящик репозитория, в который мы кидаем какие-то параметры, и он нам что-то возвращает.
Нет, он не чёрный. Ты знаешь какая там реализация и какие в конкретной реализации могут быть corner cases — на них и пишем тесты. При смене реализации все тесты обязаны быть зелёными (хотя они могут стать ненужными) и понадобится ещё раз анализировать corner cases и новые тесты.
S>Из того, что для параметров A и для параметров B возвращаются корректные результаты, никак не следует то, что корректными будут результаты для комбинации A & B. В моей практике косяки склеивания частей запроса встречались гораздо чаще косяков перепутывания < и >. S>А вопросы строгости/нестрогости неравенств упираются не столько в навыки программиста, сколько в интерпретацию требований заказчика. Ну, там, когда мы запрашиваем отчёт с 03.03.2024 по 03.03.2024 — должны ли в него попасть записи от 03.03.2024 0:00:00.000? Если да, то должна ли в него попасть запись 03.03.2024 12:30:05? Это — работа аналитика, который и будет писать в спеку конкретные < end, <= end, или там < end+1.
Хорошие вопросы, это и есть corner case анализ. Вот эти вопросы-ответы и надо задокументировать в виде тестов типа "save(...03.03.2024 12:30:05..); find(...).contains/notContains... ". А писать в тестах что в коде стоит именно "<", а не что-то другое — это бесполезное занятие. Т.к. что в коде стоит и так видно в самом коде.
S>Неспособность разработчика скопипастить выражение из спеки, да ещё и дважды подряд (в предположении, что тесты пишет тот же автор, что и основной код) — это какая то маловероятная ситуация.
Именно! Сам сказал — "скопипаситить"! Копипаста — вещь плохая. Какой смысл двойной копипасты? Какой смысл двойной копипасты? Надёжнее работать не станет. Надёжнее работать не станет. Достаточно скопипастить в прод-код, а в тесте лучше зафиксировать поведение явных конкретных примеров, понятных даже дебилам, специально для таких как я. Потому что, например, во время ревью я могу порассуждать должно ли что-то возвращаться или нет для данного случая. А у тебя ревьювер по-твоему должен открыть спеку и проверить правильность копипасты? Да, ещё стоит заметить, что спеку тоже люди пишут и опечатки делают.
Так что извини, вот как раз скопипастить любой разработчик умеет со школы. Гораздо сложнее иметь способность не копипастить.
S>·>Причём тут вредители? Я очень часто путаю == и != или < и > в коде. Поэтому ещё один способ выразить намерение кода в виде тестового примера с конкретным значением помогает поймать такие опечатки ещё до коммита. Может ты способен в сотне строк обнаружить неверный оператор или забытые строки приоритета, но я на такое не тяну. S>Не, я тоже не тяну. Поэтому не надо писать код на сотню строк, где перемешаны операции склейки запроса и написания предикатов.
У тебя всё равно где-то будет код который склеивает запрос из кучи возможных предикатов. Где ты собрался тестировать результат склейки — неясно.
S>Надо есть слона по частям. В одиночном предикате, где есть ровно одно сравнение, вы вряд ли допустите такую опечатку.
Да. Но таких предикатов будет сотни. А для конкретного findUsersByMailDomainAndGeo нужно выбрать нужные и правильно их собрать.
S>А если вы пишете всё простынкой SQL — ну, удачи, чо. Про такой код можно только молиться. Я в своей карьере такого встречал (в основном до 2010), и во всех тех местах были недотестированные ошибки.
А не важно как мы пишем. Важно как оно работает. Как ни пиши, проверять работоспособность надо в любом случае.
S>·>Так ведь ревью делается и для тестов, и для кода. Мне по коду теста сразу видно, что find(10).size() == 10 — правильно. А вот уверено решить что должно быть "rownum > 10", "rownum <= 10" или "rownum < 10" — лично я не смогу, тем более как ревьювер большого PR. S>Всё правильно, поэтому нефиг писать rownum <=10. Надо писать .addLimit(10). Его 1 (один) раз тестируют, и потом тысячи раз пользуются, полагаясь на его корректность.
Так этих таких rownum будут тысячи штук разных и немного похожих.
S>А вы сначала создали себе трудности, использовав неудачную архитектуру, а потом пытаетесь их преодолеть при помощи медленных и неполных тестов.
Да причём тут архитекутра?! Мы о тестах.
S>·>Какую сложную функцию? Вы просто переписали кривой код на прямой. Тесты тут причём? S>Сложная функция, которая превращает Criteria в набор записей. У вас это даже не функция, а объект — потому что у него есть внешние stateful dependencies.
Бред.
S>А у нас это функция, которая состоит из цепочки простых функций, каждая из которых легко тестируется.
У меня тоже. Вопрос тольк в том, что именно тестируется.
S>В вашем подходе так сделать невозможно — вы не можете взять два метода из репозитория, скажем findUsersByMailDomain() и findUsersByGeo(), и получить из них метод findUsersByMailDomainAndGeo(). S>Именно в силу выбранной архитектуры.
Что за бред. Могу, конечно. Вопрос только в том какие я/ты тесты будем писать для findUsersByMailDomainAndGeo. Ты будешь проверять, что findUsersByMailDomainAndGeo внутри себя реализована как комбинация findUsersByMailDomain и findUsersByGeo, что и так явно видно из реализации этого метода. А я напишу тест, что вот для данного набора юзеров функция возвращает нужных и не возвращает ненужных.
Поэтому если завтра придётся что-то пооптимизировать в реализации и что-то перекомбинировать как-то по-другому — ты свои тесты выкинешь и напишешь совершенно другие, оставшись без регрессии.
S>·>Да пожалуйста, если получится. S>Я не вижу причин, по которым это не получится. Вы же даже стремиться к этому отказываетесь, потому что пребываете в иллюзии невозможности
Брешешь.
S>·>Код и тесты обычно пишет один человек. Или вы парное программирование используете? S>Нет. Обычно используется всё же разделение между QA и Dev.
У вас QA пишет юнит-тесты?!! Вау!
S>·>Если код функции котороткий, то значит у тебя такхи функций овердофига. И в каждой из паре операций можно сделать очепятку. S>Поскольку отлаживаются и тестируются эти функции по отдельности, шансов пропустить опечатку гораздо ниже, чем когда у нас один метод на пятьсот строк, с нетривиальным CFG и императивными stateful зависимостями.
Если вы юзерам деливерите функции по отдельности, вам повезло. Про методы на 500 строк ты сам нафантазировал. Но мы выкатываем продукт где сотни тысяч строк. И мы всё ещё как-то должны не пропускать очепятки.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали:
P>>Итого — e2e у вас есть. ·>Есть. Но 99% этих тестов — с моками, которые ты так ненавидишь. Без моков только smoke — коротый тестирует этот самый 1% (по самым оптимистичным оценкам, скорее всего даже сильно меньше).
C чего вы взяли, что я их ненавижу? Для меня моки это инструмент крайнего случая. А у вас — основной. Потому у вас почти все тесты на моках.
P>>"а что если в колонке пусто" — вот это вроде бы уже выясняли. Вам еще хочется пройтись по кругу? ·>Выяснили, что твои тесты бесполезны, да.
Вы каким образом это тестировать будете?
P>>Накидали вы один, сделали демо — а вам сказали, это не годится. Вы другой вариант — и тот забраковали. И так до тех пор, пока не выдадите более-менее внятный. А походу дела подтягиваете спецификацию ·>Это проще, т.к. мы контролируем и спеку, и реализацию.
Непонятно, почему десяток вариантов проще одного единственного. На каждый нужно бывает прототип наклепать. И так по каждой фиче.
P>>·>В смысле фоновой процессинг в 3rd party внешней системе? Ну тогда это их проблема, поведение нашего api задокументировано. P>>Т.е. вы не понимаете, что фронтенд, мобайл, десктоп может быть частью вашей системы? ·>Тогда не понимаю проблему. В нашем клиенте тоже источник времени который можно мочить, а не дух святой.
Вы то собирались только одно место мокать. А после подсказки — два Это значит, что вы не покрыли должным образом кейс.
Вашими моками нужно покрывать вообще все причины возникновения проблемы.
Просто потому, что вы с самой проблемой не взаимодействуете.
При помощи e2e можно сделать проще — всех дел, это
1 уменьшить время сессии пользователя до разумного
2 проверить что по истечении этого периода сессия завершается
P>>Как то слишком категорично. Думаете вы один умеете конфиги сравнивать? P>>Посмотрите в коде — где у вас дефолтные значения для параметров по умолчанию? ·>Ээээ... В коде.
Вот вам и ответ.
·>Не понял. Когда прогоняешь acceptance тесты, ты прогоняешь тесты для системы версии <длинный sha>. Ровно этот же sha и деплоится на прод и гарантированно имеет все те же либы и код, с точностью до бита.
Я вам про то, что вы не знаете как ведет себя ваша система. Нет способа зафиксировать каждое отдельное свойство.
У вас все равно будет разница в конфигах, разница в нагрузке, бд, сети и тд
Вот эти вещи вносят детерминизм
> Следовательно и константы имеют идентичное значение. В эпоху докер-имаджей иметь такие проблемы как у вас — должно быть просто стыдно.
Снова телепатия.
P>>Мы только что выяснили, что моками вы никаких гарантий дать не можете — все равно придется ждать деплоя на прод и смотреть в мониторинг когда нагрузка стала той самой ·>Зачем? Нагрузочное тестирование можно и в отдельном перф-енве сделать. Путём ускоренного проигрывания событий из вчерашнего прода.
Ну круто — после моков бежать прокручивать вчерашние события из прода. Вот он бенефит!
P>>Это все в контексте дизайна который определяется тестами. Написали под моки — значит пропихивать зависимости так, а не иначе. И всё, приплыли. ·>Ты так говоришь, будто это что-то плохое.
Самосбывающееся пророчество — сначала вы так пишете код, а потом вещаете что моки это единственный вариант.
P>>У вас именно так — вы считаете моки единственным инструментом для работы с зависимостями. ·>Других ты не смог предложить.
Наоборот.
P>>Скорее это вы почему то не понимаете, что мир не сводится к вашему проекту. ·>Так вы сами себе в ногу стреляете своим DateTime.Now().
Один тест будет написан немного иначе, не так как у вас. За счет упрощения огромного количества других тестов.
P>>Подозреваю, вы и под глобальными переменными вы подразумеваете чтото другое. ·>А что я должен подразумевать по-твоему?
Подождем пару месяцев и выясним.
P>>Очевидно, что нет. Только зафиксированых на момент написания теста. И этих проблем он обнаруживает на два порядка больше мониторинга. ·>А разница в том, что мы, благодаря мокам и дизайну, можем обнаружить все эти зафиксированные проблемы ещё до планирования релиза, а вы — только после как "Юзеры репортают проблемы". Зато всё по фаулеру!
Вы только что сказали, что поверх моков вам нужны прогон тестов вчерашнего прода.
P>>Вы снова занялись фантазированием. Вот проверили вы список проблем со значением в конфиге, условно, 5 минут. P>>А потом на проде вылезла новая проблема ровнёхонько через месяц P>>- например, задачи на создание стейтмента создаются, но висят ибо очередь забита чего у вас в тестах не было из за недостаточной загрузки ·>Забитость очереди, ещё раз, тривиально детектится мониторингом.
Еще раз, медленно — мониторинг сработает на проде. А вам надо дать гарантию, что не будет никакого срабатывания мониторинга.
·>Я не понял как ты предлагаешь обнаружить это. Создание statement происходит для всех в конце месяца, что для юзеров, что для твоих тестов. Т.е. твой тест если и упадёт, то в лучшем случае ровно в тот же момент, что и у юзеров начнутся проблемы. Это уже слишком поздно.
Что в вашем statement такое особенное?
P>>Отдельные части это технически вообще как юнит-тесты. ·>Т.е. поднимается вся инфра? Ведь контроллер может использовать ВСЁ.
И что с того?
·>Что за "параметры"? Моки, ведь, да?
Можно и моки, если сильно хочется. Что тут такого?
P>>Прогон тестов на проде это не моя придумка. Уже лет десять назад было довольно популярной практикой. ·>Где? Есть статьи?
Без понятия. Зачем вам статьи? Как вы поступили со статьей про clen architecture ?
P>>А acceptance сколько покрывает? ·>Почти всё. Но с моками.
Что конкретно вы там мокаете?
P>>Нагрузка, данные, энвайрмент, время, сеть — отсюда и лезет недетерминизм. ·>Нагрузка и данные недетерминизм не создают.
Еще как создают
— в распределенном приложении нагрузка всегда вносит хаос
— данные подкидывают вам комбинации которые вы не учли. Как будет работать система на таких комбинациях — хрен его знает
·>Энвайрмент, время — он у вас лезет, потому что вы сами его создаёте этим же своим DateTime.Now() и бесконтрольными конфигами.
Вы лучше расскажите, какую проблему не получится зафиксировать тестов если есть вызов now() в контроллере.
Вы который месяц говорите что это ужос-ужос, но проблему указать не торопитесь
P>>Именно для этого некоторые конторы в момент максимальной нагрузки запускают тесты прода. Это не шутка. P>>Таким образом тестируется система в нагрузке. ·>Жуть.
Что вас тут пугает?
P>>Например, потому что запускается в момент большой загрузки. ·>Это гораздо проще и с более надёжным результатом вызвать в тестовом окружении.
Тесты на проде запускаются не вместо тестового окружения, а после тестового окружения.
Неужели непонятно?
P>>Так и у меня их нет. И мой подход основан на пост-условиях. Это почти что статическая типизация, с некоторым упрощением. ·>Ага-ага Почти беременна, с некоторым упрощением.
Вы что, про эйфель не слышали?
P>>А вот тесты здесь играют совсем другую роль — они фиксируют наличие пост-условия. ·>Не фиксируют, наивый юноша.
Расскажите, что ваши тесты делают. Посмеёмся.
P>>work as coded это если копировать sql из приложения в тесты. ·>Так ты не стесняйся, покажи свой пример кода для которого ты показал свой тест с pattern. Получится ровно копипаст.
Никакого копипаста — построение запроса это рендеринг sql по модели в json. Все что можно сказать заранее, что запрос будет select, другие не нужны.
P>>А если в приложении билдер, а в тестах — его выхлоп на конкретных параметрах, то мы напрямую проверяем маппер — о большинстве проблем вы узнаете еще до запуска на реальной базе данных ·>Именно, что у тебя ровно то же, что и у меня: "на конкретных параметрах". ЧТД. Статическая типизация, май год! А как дышал...
OMG! Вы как раз напрямую маппер не проверяете. Этой части у вас нет. Общая часть у вас и у меня только те конкретные примеры, в которых проблемные комбинации отсутствуют.
P>>Ну так конкретных примеров то нет. А раз так — то используем "1 типизация, пост-условия, инварианты, пред-условия, структурная эквивалентность" ·>Строкой выше было "на конкретных параметрах", куда делось?!
Это ваша телепатия сломалась. Конкретные примеры не содержат никаких проблемных комбинаций.
P>>Вы пока не смогли пример адекватный привести, который сломает хотя бы примитивный limit 10. ·>Привёл в ответе Sinclair с ?:-оператором.
Эта же проблема есть и у вас.
P>>Потому, что вы изначально думаете в единицах моков, и почти всё к ним и сводите. ·>Ага, да ты телепат — знаешь что я думаю. А показать-то есть что?
Мне ваши мысли не нужны — вы сами пишете, что у вас 99% тестов на моках.
P>>·>У меня ровно те же комбинации, что будут и в твоих тестах. Отличается только часть в ассертах. Вместо проверки синтаксиса — sql-код запускается и валидируется интересный с тз бизнеса результат. P>>Комбинации в бд не показывают проблему. Следовательно, вы вытащите её на прод. ·>Если у меня не показывают, то и у тебя не показывают. Об чём спор-то?
Ну и логика
P>>·>Понятна конечно, я это тебе и говорил. Вот только постусловие ты проверить не сможешь тестом. P>>Тестом я не проверяю пост-условие. Тестом я проверяю, что оно на месте. ·>Не проверяешь, Райс мешает.
Буквально — мешает. Только у меня на этом держится вспомогательая вещь, а у вас — вообще всё.
P>>Результат запроса не влазит в память. ·>Причём тут isOdd?
При том, что для четности вам нужно знание о свойствах входного параметра. И тогда вы фиксируете свойство выхода.
Для правильных лимитов точно так же нужно знание о свойствах входа. И тогда вы фиксируете свойство выхода.
Но вот переборы комбинаций вам ничего из этого не дадут.
Здравствуйте, ·, Вы писали: S>>"Аспекты поведения" можно зафиксировать e2e-тестами только в исчезающе малом количестве случаев. ·>Почему? Ты, наверное, под "зафиксировать" что-то суровое понимаешь.
Понимаю ширину покрытия.
S>>Все остальные варианты исключаются дизайном функции построения запроса. ·>ИМЕННО! Твой тест тестирует только один небольшой сценарий для небольшого частного случая. Как и мой.
Но только в моём случае других "частных случаев" нет — в силу дизайна. А в вашем — есть. ·>И никаких универсальных гарантий или проверок того что "пол базы не загружается" он давать не может в принципе. Это невозможно обеспечить тестами. Никакими, т.к. остальных вариантов будет примерно ∞ — 1.
Хм, я вроде прямо по шагам показал, как мы исключаем "остальные варианты".
·>ИМЕННО!! Тесты-то тут не причём. А следовательно, отнюдь, мой тест Такое Же Быдло™ как и Ваш.
Тест — да. Архитектура — нет. ·>Однако, мой тест менее хрупкий и покрывает немного больше, т.к. выражает намерение напрямую.
Там не очень понятно, какое именно намерение изображается. Вы тестируете одновременно реализацию критериев отбора и ограничения страницы.
S>>Как вариант (если у нас достаточно развитая платформа) — сигнатурой функции, которая обязуется возвращать ILimitedQueryable<...>. ·>Если бы у бабушки были бы... Но давай-ка из страны розовых пони возвращайся.
Непонятное утверждение. Вы полагаете, что такую штуку нельзя выразить с помощью системы типов?
Можно. Но в большинстве случаев это непрактично — архитектура "конвейер функций" достаточно устойчива и без подобных мер формального доказательства корректности.
·>Наполнением теста занимается окружение самого теста и аккуратно подготавливается и не зависит от реализации. Хрупкость-то откуда? Тестовые данные должны соответствовать бизнес-требованиям. А хрупкость — это о деталях реализации. Если тест проходил, то он должен проходить в любом случае, вне зависимо от реализации, если требования не меняются; и наоборот, тест должен падать, если требования меняются — это и есть прочный тест.
Это касается e2e-тестов. Если бы не ресурсные ограничения, я топил бы за них — но ограничения есть. Поэтому приходится структурировать задачу таким образом, чтобы тестировать её кусочки.
И вы, кстати, делаете точно так же — ваши моки описывают совершенно конкретный дизайн системы, который никакого отношения к бизнес-требованиям не имеет.
И точно также при смене "реализации" — например, при выделении части кода компонента в стратегию — вам придётся переписывать тесты. Несмотря на то, что бизнес-требования никак не поменялись.
Опять же — вот вы приводите пример оптимизации, которая типа ломает addLimit. Ну, так эта оптимизация — она откуда взялась? Просто программисту в голову шибануло "а перепишу-ка я рабочий код, а то давненько у нас на проде ничего не падало"? Нет, пришёл бизнес и сказал "вот в таких условиях ваша система тормозит. Почините". И программист починил — новая реализация покрывает новые требования.
·>Это ровно то же грозит и вам. Т.к. у тебя "проверяется только для одного конкртетного запроса с одним из заданных в тесте criteria", с чем ты согласился.
Нет, потому что в нашей архитектуре разные части конвеера тестируются отдельно.
·>Так это вы и предлагаете: "Запрет реализуется орг.мерами — от обучения разрабочиков, до код ревью и отбора прав на соответствующую ветку репозитория".
Ну, всё верно. Просто вы-то предлагаете то же самое, только более дорогим способом.
S>>Ну так это сделано намеренно. Потому что те "дополнительные аспекты", которые тщится проверять ваш тест, не нужно проверять в каждом из юнит-тестов. ·>Т.е. всё то же и у меня, и у вас.
Нет, отчего же. Умение СУБД исполнять SQL мне проверять не нужно — какой-нибудь SQLite покрыт тестами в шесть слоёв. Мне достаточно пары smoke-тестов, чтобы убедиться, что запросы вообще доезжают до СУБД. А после этого можно просто тестировать порождение запросов. А вам приходится в каждом тесте проверять, что запрос доезжает до базы, что она его может разобрать, что она построит корректный результат, что результат доедет до приложения, и что код репозитория способен корректно превратить этот результат в типизированные данные приложения.
·>Каким образом проверить-то? Я предлагаю — в виде явного тупого конкретного примера "save(vasya); assert find(petya).isEmpty()" — вот такой тест тупой и очевидный, даже дебилу понятно что он правильный — как раз по мне.
Это иллюзия.
Может быть, find() просто делает return empty().
И даже если вы добавите assert find(vasya) == vasya, это мало что изменит — потому что нет гарантии, что find(vasilisa) != vasya.
Чтобы нормально покрыть тестами ваш find(), придётся отказаться трактовать его как чёрный ящик.
·>А вот тест который проверит наличие предиката конкретно ==, а не != — я не вытяну. Вдруг там где-нибудь двойное отрицание закрадётся...
Вы себя недооцениваете.
·>Нет, он не чёрный. Ты знаешь какая там реализация и какие в конкретной реализации могут быть corner cases — на них и пишем тесты. При смене реализации все тесты обязаны быть зелёными (хотя они могут стать ненужными) и понадобится ещё раз анализировать corner cases и новые тесты.
Раз мы всё равно "ещё раз" анализируем corner cases, то ничего плохого от покраснения тестов не будет. Наоборот — это повод ещё раз внимательно пересмотреть наши corner cases.
Плохо не когда рабочий код красится в красный, а когда нерабочий красится в зелёный.
·>Хорошие вопросы, это и есть corner case анализ. Вот эти вопросы-ответы и надо задокументировать в виде тестов типа "save(...03.03.2024 12:30:05..); find(...).contains/notContains... ".
Бизнес-аналитик не будет вам писать тесты. Увы.
·>А писать в тестах что в коде стоит именно "<", а не что-то другое — это бесполезное занятие. Т.к. что в коде стоит и так видно в самом коде.
·>У тебя всё равно где-то будет код который склеивает запрос из кучи возможных предикатов. Где ты собрался тестировать результат склейки — неясно.
Я же писал — берём и проверяем результат склейки. То, что при всех мыслимых комбинациях предикатов породится корректный SQL, нам тестировать не надо — это свойство библиотеки, она заведомо делает то, что обещала.
А нам нужно просто проверить покомпонентно, что ни один из кусочков рецепта не забыт.
·>Да. Но таких предикатов будет сотни. А для конкретного findUsersByMailDomainAndGeo нужно выбрать нужные и правильно их собрать.
Не понял смысл этой фразы. findUsersByMailDomainAndGeo — это просто линейная комбинация S>>А если вы пишете всё простынкой SQL — ну, удачи, чо. Про такой код можно только молиться. Я в своей карьере такого встречал (в основном до 2010), и во всех тех местах были недотестированные ошибки. ·>А не важно как мы пишем. Важно как оно работает. Как ни пиши, проверять работоспособность надо в любом случае.
S>>Всё правильно, поэтому нефиг писать rownum <=10. Надо писать .addLimit(10). Его 1 (один) раз тестируют, и потом тысячи раз пользуются, полагаясь на его корректность. ·>Так этих таких rownum будут тысячи штук разных и немного похожих.
Непонятно. Все тыщи — это фрагменты запроса, каждый из которых — простой.
Из них строятся комбинации, тоже простые.
И так — рекурсивно. ·>Да причём тут архитекутра?! Мы о тестах.
Эмм, напоминаю: обсуждается ФП-архитектура, где моки не нужны, т.к. весь код — чистый и композируемый. Супротив stateful OOP, в котором всё можно тестировать только моками. И частный случай этого — БД как внешняя stateful-зависимость.
·>Бред.
·>У меня тоже.
Покажите.
·>Что за бред. Могу, конечно.
Нет, не можете. Потому что у вас это не функции в смысле FP, которые превращают Criteria в Query, а методы, которые превращают Criteria в Result.
·>Вопрос только в том какие я/ты тесты будем писать для findUsersByMailDomainAndGeo. Ты будешь проверять, что findUsersByMailDomainAndGeo внутри себя реализована как комбинация findUsersByMailDomain и findUsersByGeo, что и так явно видно из реализации этого метода. А я напишу тест, что вот для данного набора юзеров функция возвращает нужных и не возвращает ненужных.
Ну всё правильно — и вместо N + M тестов вы будете писать N*M, потому что в реализации этого метода вы не будете повторно использовать код ни findUsersByMailDomain, ни findUsersByGeo. ·>Поэтому если завтра придётся что-то пооптимизировать в реализации и что-то перекомбинировать как-то по-другому — ты свои тесты выкинешь и напишешь совершенно другие, оставшись без регрессии.
А вы по факту останетесь без тестов, т.к. N*M вы писать вспотеете, и останетесь с набором зелёных тестов даже в случае косяков в corner cases.
·>У вас QA пишет юнит-тесты?!! Вау!
Не то, чтобы "у нас". Но у меня есть опыт работы с разделением обязанностей QA и разработчиков.
S>Если вы юзерам деливерите функции по отдельности, вам повезло.
Нам не нужно "деливерить юзерам". На всякий случай напомню, что юзерам и на ваши моки совершенно наплевать, их интересует только внешне наблюдаемое e2e-поведение. И репозитории им тоже неитересны. Вы же деливерите пользователям не репозиторий, а всё приложение.
Это не повод отказываться от всех иных форм тестирования. И вы же сами ровно это же и подтверждаете — почти все описанные вами тесты очень слабо связаны с "деливери пользователям".
Так и тут — если уж мы решили провести границу тестирования вокруг произвольно выбранного нами компонента системы — скажем, репозитория, то что нам помешает порезать этот репозиторий на K компонент?
S>Про методы на 500 строк ты сам нафантазировал.
Ну, вы покажите код вашего репозитория с поиском по сложным критериям — и посмотрим. Может у вас там под капотом ровно то же самое
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>>>"Аспекты поведения" можно зафиксировать e2e-тестами только в исчезающе малом количестве случаев. S>·>Почему? Ты, наверное, под "зафиксировать" что-то суровое понимаешь. S>Понимаю ширину покрытия.
Ну например: "findUsersByMailDomainAndGeo"
1. должно среди данного списка юзеров найти только совпадающих по домену и локации — это аспект поведения.
2. должно комбинировать findUsersByMailDomain и findUsersByMailGeo предикатом and — это детали реализации.
Я предлагаю тестировать 1, а ты топишь за 2.
Причём тут ширина?
S>>>Все остальные варианты исключаются дизайном функции построения запроса. S>·>ИМЕННО! Твой тест тестирует только один небольшой сценарий для небольшого частного случая. Как и мой. S>Но только в моём случае других "частных случаев" нет — в силу дизайна. А в вашем — есть.
Может быть, и тест это проверить никак не может.
S>·>И никаких универсальных гарантий или проверок того что "пол базы не загружается" он давать не может в принципе. Это невозможно обеспечить тестами. Никакими, т.к. остальных вариантов будет примерно ∞ — 1. S>Хм, я вроде прямо по шагам показал, как мы исключаем "остальные варианты".
И как это противоречит "никакими _тестами_"?
S>·>ИМЕННО!! Тесты-то тут не причём. А следовательно, отнюдь, мой тест Такое Же Быдло™ как и Ваш. S>Тест — да. Архитектура — нет.
А об архитектуре я речь пытаюсь не вести, но вы всё не в ту степь сворачиваете.
S>·>Однако, мой тест менее хрупкий и покрывает немного больше, т.к. выражает намерение напрямую. S>Там не очень понятно, какое именно намерение изображается. Вы тестируете одновременно реализацию критериев отбора и ограничения страницы.
Я не тестирую реализацию, я тестирую что метод ожидается должен делать.
S>>>Как вариант (если у нас достаточно развитая платформа) — сигнатурой функции, которая обязуется возвращать ILimitedQueryable<...>. S>·>Если бы у бабушки были бы... Но давай-ка из страны розовых пони возвращайся. S>Непонятное утверждение. Вы полагаете, что такую штуку нельзя выразить с помощью системы типов?
Что "это непрактично" и смысла обсуждать не имеет.
S>·>Наполнением теста занимается окружение самого теста и аккуратно подготавливается и не зависит от реализации. Хрупкость-то откуда? Тестовые данные должны соответствовать бизнес-требованиям. А хрупкость — это о деталях реализации. Если тест проходил, то он должен проходить в любом случае, вне зависимо от реализации, если требования не меняются; и наоборот, тест должен падать, если требования меняются — это и есть прочный тест. S>Это касается e2e-тестов. Если бы не ресурсные ограничения, я топил бы за них — но ограничения есть. Поэтому приходится структурировать задачу таким образом, чтобы тестировать её кусочки.
Именно. Но даже когда тестируем кусочки — надо не отвлекаться от бизнес-задачи.
S>И вы, кстати, делаете точно так же — ваши моки описывают совершенно конкретный дизайн системы, который никакого отношения к бизнес-требованиям не имеет.
Давай определимся, "findUsersByMailDomainAndGeo" — имеет или нет?
S>И точно также при смене "реализации" — например, при выделении части кода компонента в стратегию — вам придётся переписывать тесты. Несмотря на то, что бизнес-требования никак не поменялись.
Зачем? Сразу после выделения (т.е. рефакторинг прод-кода) — тесты не трогаем, они должны остаться без изменений и быть зелёными. Следующим шагом можно отрефакторить тесты, перенеся их в новый вынесенный юнит, уже не трогая прод код.
S>Опять же — вот вы приводите пример оптимизации, которая типа ломает addLimit. Ну, так эта оптимизация — она откуда взялась? Просто программисту в голову шибануло "а перепишу-ка я рабочий код, а то давненько у нас на проде ничего не падало"? Нет, пришёл бизнес и сказал "вот в таких условиях ваша система тормозит. Почините". И программист починил — новая реализация покрывает новые требования.
Ну да. Но все старые тесты остаются как есть, зелёными. Создаём новые тесты для новых требований, но не трогаем старые. Когда задача завершена, и очень хочется, то можно проанализировать какие тесты стали избыточными и их просто выкинуть.
S>·>Это ровно то же грозит и вам. Т.к. у тебя "проверяется только для одного конкртетного запроса с одним из заданных в тесте criteria", с чем ты согласился. S>Нет, потому что в нашей архитектуре разные части конвеера тестируются отдельно.
Это не избавляет от необходимости тестировать части в сборе. И проблема в том, что тестировать весь конвеер целиком "но ресурсные ограничения есть". Упс.
S>·>Так это вы и предлагаете: "Запрет реализуется орг.мерами — от обучения разрабочиков, до код ревью и отбора прав на соответствующую ветку репозитория". S>Ну, всё верно. Просто вы-то предлагаете то же самое, только более дорогим способом.
Нет.
S>·>Т.е. всё то же и у меня, и у вас. S>Нет, отчего же. Умение СУБД исполнять SQL мне проверять не нужно — какой-нибудь SQLite покрыт тестами в шесть слоёв. Мне достаточно пары smoke-тестов, чтобы убедиться, что запросы вообще доезжают до СУБД. А после этого можно просто тестировать порождение запросов. А вам приходится в каждом тесте проверять, что запрос доезжает до базы, что она его может разобрать, что она построит корректный результат, что результат доедет до приложения, и что код репозитория способен корректно превратить этот результат в типизированные данные приложения.
Всё верно. Суть в том, что надо тестировать не умение субд, не порождение запросов, не доезжание, а что findUsersByMailDomainAndGeo действительно ищет юзеров по домену и гео. А как он это делает, через sql, через cql, через linq или через чёрта лысого — пофиг.
S>·>Каким образом проверить-то? Я предлагаю — в виде явного тупого конкретного примера "save(vasya); assert find(petya).isEmpty()" — вот такой тест тупой и очевидный, даже дебилу понятно что он правильный — как раз по мне. S>Это иллюзия.
Иллюзия чего? Я вижу, что пример правильный — это любой дебил неиллюзорно видит.
Это у тебя есть иллюзия, что имея тест ты доказываешь что твой _код_ правильный. Вот это очень опасная иллюзия. Тест должен быть очевидно правильный, а для доказательства правильности кода тесты вообще никак не подходят.
S>Может быть, find() просто делает return empty().
Да пускай, пофиг. Зато этот конкретный сценарий — правильный, и он будет всегда правильный, независимо от реализации, даже для return empty, это инвариант — и это круто.
S>И даже если вы добавите assert find(vasya) == vasya, это мало что изменит — потому что нет гарантии, что find(vasilisa) != vasya.
Конечно. Но ты забыл, что никакие тесты такую гарантию дать и не могут. Твои тоже.
А изменит это то, что теперь у нас есть инварианты и можно спокойно менять код под новые требования и оптимизации, имея подушку регрессии.
S>Чтобы нормально покрыть тестами ваш find(), придётся отказаться трактовать его как чёрный ящик.
Нет. Тесты — продукт corner case анализа, документация к коду, а не инструмент доказательства корректности и обеспечения гарантий.
S>·>А вот тест который проверит наличие предиката конкретно ==, а не != — я не вытяну. Вдруг там где-нибудь двойное отрицание закрадётся... S>Вы себя недооцениваете.
Ах если бы. Как минимум раз в неделю подобные опечатки. Ловятся только тестами. if/else, null/notnull, return/continue, отвлёкся на кывт — и запутался в двойных отрицаниях.
S>·>Нет, он не чёрный. Ты знаешь какая там реализация и какие в конкретной реализации могут быть corner cases — на них и пишем тесты. При смене реализации все тесты обязаны быть зелёными (хотя они могут стать ненужными) и понадобится ещё раз анализировать corner cases и новые тесты. S>Раз мы всё равно "ещё раз" анализируем corner cases, то ничего плохого от покраснения тестов не будет. Наоборот — это повод ещё раз внимательно пересмотреть наши corner cases. S>Плохо не когда рабочий код красится в красный, а когда нерабочий красится в зелёный.
И то, и то плохо. Если покрасился в красный, это значит мы что-то меняем в поведении и это может привести к неожиданным изменениям у юзеров.
S>·>Хорошие вопросы, это и есть corner case анализ. Вот эти вопросы-ответы и надо задокументировать в виде тестов типа "save(...03.03.2024 12:30:05..); find(...).contains/notContains... ". S>Бизнес-аналитик не будет вам писать тесты. Увы.
Не писать, но если ему задать вопрос который описывает given-часть теста — то аналитик ответит какой правильный результат мне прописать в verify-части — contains или notContains.
S>·>У тебя всё равно где-то будет код который склеивает запрос из кучи возможных предикатов. Где ты собрался тестировать результат склейки — неясно. S>Я же писал — берём и проверяем результат склейки. То, что при всех мыслимых комбинациях предикатов породится корректный SQL, нам тестировать не надо — это свойство библиотеки, она заведомо делает то, что обещала. S>А нам нужно просто проверить покомпонентно, что ни один из кусочков рецепта не забыт.
Корректный в смысле синтаксис не сломан? Да пофиг, такое действительно не особо важно проверять.
S>·>Да. Но таких предикатов будет сотни. А для конкретного findUsersByMailDomainAndGeo нужно выбрать нужные и правильно их собрать. S>Не понял смысл этой фразы. findUsersByMailDomainAndGeo — это просто линейная комбинация
Комбинация чего? И почему именно этого? Комбинировать можно столько всего... Проверять нужно что эта самая комбинация — та, которая ожидается.
S>>>Всё правильно, поэтому нефиг писать rownum <=10. Надо писать .addLimit(10). Его 1 (один) раз тестируют, и потом тысячи раз пользуются, полагаясь на его корректность. S>·>Так этих таких rownum будут тысячи штук разных и немного похожих. S>Непонятно. Все тыщи — это фрагменты запроса, каждый из которых — простой. S>Из них строятся комбинации, тоже простые.
Из тыщи фрагментов можно построить 21000 комбинаций. Но только сотня из них — та, которая нужна.
S>И так — рекурсивно.
Да пожалуйста, строй как хошь. Речь о том как это тестировать.
S>·>Да причём тут архитекутра?! Мы о тестах. S>Эмм, напоминаю: обсуждается ФП-архитектура, где моки не нужны, т.к. весь код — чистый и композируемый. Супротив stateful OOP, в котором всё можно тестировать только моками. И частный случай этого — БД как внешняя stateful-зависимость.
моки это не про state, а про передачу параметров. Для тестирования репо+бд моки не нужны вообще-то. Моки нужны для экономии ресурсов.
S>·>У меня тоже. S>Покажите.
Что показать?
S>·>Что за бред. Могу, конечно. S>Нет, не можете. Потому что у вас это не функции в смысле FP, которые превращают Criteria в Query, а методы, которые превращают Criteria в Result.
У вас тоже они где-то есть.
S>·>Вопрос только в том какие я/ты тесты будем писать для findUsersByMailDomainAndGeo. Ты будешь проверять, что findUsersByMailDomainAndGeo внутри себя реализована как комбинация findUsersByMailDomain и findUsersByGeo, что и так явно видно из реализации этого метода. А я напишу тест, что вот для данного набора юзеров функция возвращает нужных и не возвращает ненужных. S>Ну всё правильно — и вместо N + M тестов вы будете писать N*M, потому что в реализации этого метода вы не будете повторно использовать код ни findUsersByMailDomain, ни findUsersByGeo.
Почему не буду?
S>·>Поэтому если завтра придётся что-то пооптимизировать в реализации и что-то перекомбинировать как-то по-другому — ты свои тесты выкинешь и напишешь совершенно другие, оставшись без регрессии. S>А вы по факту останетесь без тестов, т.к. N*M вы писать вспотеете, и останетесь с набором зелёных тестов даже в случае косяков в corner cases.
Фантазии.
S>·>У вас QA пишет юнит-тесты?!! Вау! S>Не то, чтобы "у нас". Но у меня есть опыт работы с разделением обязанностей QA и разработчиков.
Именно ютесты? Когда QA пишет acceptance — это круто. Но они пишут только само тело given/verify, а нижележащие действия (т.е. реализации шагов в тесте) пишут разрабы.
S>>Если вы юзерам деливерите функции по отдельности, вам повезло. S>Нам не нужно "деливерить юзерам". На всякий случай напомню, что юзерам и на ваши моки совершенно наплевать, их интересует только внешне наблюдаемое e2e-поведение. И репозитории им тоже неитересны. Вы же деливерите пользователям не репозиторий, а всё приложение. S>Это не повод отказываться от всех иных форм тестирования. И вы же сами ровно это же и подтверждаете — почти все описанные вами тесты очень слабо связаны с "деливери пользователям". S>Так и тут — если уж мы решили провести границу тестирования вокруг произвольно выбранного нами компонента системы — скажем, репозитория, то что нам помешает порезать этот репозиторий на K компонент?
Ну "findUsersByMailDomainAndGeo" — это поведение или что? А вот contains("top(10)") — это по-моему совершенно не поведение.
S>>Про методы на 500 строк ты сам нафантазировал. S>Ну, вы покажите код вашего репозитория с поиском по сложным критериям — и посмотрим. Может у вас там под капотом ровно то же самое
Ну так да, уж несколько месяцев объясняю, что код похожий. Тесты другие.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали:
·>Что за бред. Могу, конечно. Вопрос только в том какие я/ты тесты будем писать для findUsersByMailDomainAndGeo. Ты будешь проверять, что findUsersByMailDomainAndGeo внутри себя реализована как комбинация findUsersByMailDomain и findUsersByGeo, что и так явно видно из реализации этого метода. А я напишу тест, что вот для данного набора юзеров функция возвращает нужных и не возвращает ненужных.
"не возвращает ненужных" — мешает теорема Райса. Все что сложнее одной таблицы уже просто так пальцем на заткнуть.
·>Поэтому если завтра придётся что-то пооптимизировать в реализации и что-то перекомбинировать как-то по-другому — ты свои тесты выкинешь и напишешь совершенно другие, оставшись без регрессии.
Если без регрессии то это же хорошо. А вы что, в регрессии пользу видите?
Здравствуйте, ·, Вы писали:
·>Ну например: "findUsersByMailDomainAndGeo" ·>1. должно среди данного списка юзеров найти только совпадающих по домену и локации — это аспект поведения. ·>2. должно комбинировать findUsersByMailDomain и findUsersByMailGeo предикатом and — это детали реализации. ·>Я предлагаю тестировать 1, а ты топишь за 2. ·>Причём тут ширина?
Ширина при том, что findUsersByMailDomainAndGeo имеет как минимум четыре "особых точки" в зависимости от того, какой из критериев домена или географии установлен в "any".
Если мы продолжим наворачивать туда критерии (как оно обычно бывает в системах с "настраиваемой отчётностью"), то очень быстро окажется, что у нас нет ресурсов для выполнения e2e по всем-всем мыслимым комбинациям.
·>Может быть, и тест это проверить никак не может.
Ну, то есть проблема всё же в дизайне, а не в тестах.
·>А об архитектуре я речь пытаюсь не вести, но вы всё не в ту степь сворачиваете.
Ну, если ключи искать под фонарём — тогда да.
·>Я не тестирую реализацию, я тестирую что метод ожидается должен делать.
·>Что "это непрактично" и смысла обсуждать не имеет.
Т.е. и без таких наворотов мы получаем приемлемое качество. Ч.Т.Д.
S>>И вы, кстати, делаете точно так же — ваши моки описывают совершенно конкретный дизайн системы, который никакого отношения к бизнес-требованиям не имеет. ·>Давай определимся, "findUsersByMailDomainAndGeo" — имеет или нет?
Имеет конечно. У нас в бизнес требованиях указано, что могут быть ограничения на почтовый домен (по точному совпадению, по частичному совпадению) и по географии (на уровне зоны/страны/региона/населённого пункта). Если задано более одного ограничения — применяются все из них.
·>Зачем? Сразу после выделения (т.е. рефакторинг прод-кода) — тесты не трогаем, они должны остаться без изменений и быть зелёными.
Они не могут остаться без изменений; у вас там стратегию нужно замокать. Ну, вот как с датой — перенос её в TimeSource требует от вас в рамках теста подготовить нужный источник времени.
S>>Опять же — вот вы приводите пример оптимизации, которая типа ломает addLimit. Ну, так эта оптимизация — она откуда взялась? Просто программисту в голову шибануло "а перепишу-ка я рабочий код, а то давненько у нас на проде ничего не падало"? Нет, пришёл бизнес и сказал "вот в таких условиях ваша система тормозит. Почините". И программист починил — новая реализация покрывает новые требования. ·>Ну да. Но все старые тесты остаются как есть, зелёными. Создаём новые тесты для новых требований, но не трогаем старые. Когда задача завершена, и очень хочется, то можно проанализировать какие тесты стали избыточными и их просто выкинуть.
Точно так же, как и в FP-случае.
·>Это не избавляет от необходимости тестировать части в сборе. И проблема в том, что тестировать весь конвеер целиком "но ресурсные ограничения есть". Упс.
Конвеер тестируется в длину, а не в ширину. Для этого достаточно 1 (одного) теста.
·>Нет.
·>Всё верно. Суть в том, что надо тестировать не умение субд, не порождение запросов, не доезжание, а что findUsersByMailDomainAndGeo действительно ищет юзеров по домену и гео. А как он это делает, через sql, через cql, через linq или через чёрта лысого — пофиг.
В теории — да, но на практике "через чёрта лысого" вы просто не покроете тестами никогда.
S>>Это иллюзия. ·>Иллюзия чего? Я вижу, что пример правильный — это любой дебил неиллюзорно видит.
S>>Может быть, find() просто делает return empty(). ·>Да пускай, пофиг. Зато этот конкретный сценарий — правильный, и он будет всегда правильный, независимо от реализации, даже для return empty, это инвариант — и это круто.
S>>И даже если вы добавите assert find(vasya) == vasya, это мало что изменит — потому что нет гарантии, что find(vasilisa) != vasya. ·>Конечно. Но ты забыл, что никакие тесты такую гарантию дать и не могут. Твои тоже.
Формально — да, не могут.
На практике мы можем проверить не только статус тестов, но и test coverage. В общем случае по test coverage никаких выводов делать нельзя, т.к. результат зависит от путей исполнения. Но когда мы принудительно линеаризуем исполнение, из code coverage можно делать выводы с высокой степенью надёжности. Вот в вашем примере про добавление оптимизированной ветки в query — ну и прекрасно, она у нас уедет в buildWhere, и там мы проверим результаты тестами, а code coverage покажет нам, что для новой ветки тестов недостаточно. При этом addLimit останется покрытым, потому что он добавляется в отдельном от ветвления между "основным" и "оптимизированным" вариантами месте.
S>>Чтобы нормально покрыть тестами ваш find(), придётся отказаться трактовать его как чёрный ящик. ·>Нет. Тесты — продукт corner case анализа, документация к коду, а не инструмент доказательства корректности и обеспечения гарантий.
Хм. А как вы обеспечиваете гарантии?
·>И то, и то плохо. Если покрасился в красный, это значит мы что-то меняем в поведении и это может привести к неожиданным изменениям у юзеров.
S>>Бизнес-аналитик не будет вам писать тесты. Увы. ·>Не писать, но если ему задать вопрос который описывает given-часть теста — то аналитик ответит какой правильный результат мне прописать в verify-части — contains или notContains.
А если ему задать вопрос про то, почему должен быть такой результат — он скажет, каким должен быть оператор
·>Корректный в смысле синтаксис не сломан? Да пофиг, такое действительно не особо важно проверять.
Синтаксис и семантика.
·>Комбинация чего? И почему именно этого?
комбинация предикатов по домену и по географии. И потому, что это задано бизнес-требованиями.
·>Комбинировать можно столько всего... Проверять нужно что эта самая комбинация — та, которая ожидается.
S>>Из них строятся комбинации, тоже простые. ·>Из тыщи фрагментов можно построить 21000 комбинаций. Но только сотня из них — та, которая нужна.
Ну вот нам достаточно один раз описать способ построения этой комбинации, и в дальнейшем проверять именно его.
·>Да пожалуйста, строй как хошь. Речь о том как это тестировать.
Вроде всё написал.
·>моки это не про state, а про передачу параметров. Для тестирования репо+бд моки не нужны вообще-то. Моки нужны для экономии ресурсов.
Эмм, ваша "модельная БД" — это тоже мок. Нет никакой разницы с т.з. архитектуры тестирования, то ли вы пишете мок вручную и хардкодите в него ответы на find(vasya), то ли вы его описываете декларативно с помощью мок-фреймворка, то ли берёте "квазинастоящую БД" и напихиваете данными перед тестом.
Это всё — про одно и то же "у меня есть stateful dependency, и я моделирую этот state перед каждым тестом". ·>Что показать?
Код.
·>У вас тоже они где-то есть.
Они — на самом верху, в конце конвеера, и их достаточно проверить при помощи немногих e2e тестов.
S>>Ну всё правильно — и вместо N + M тестов вы будете писать N*M, потому что в реализации этого метода вы не будете повторно использовать код ни findUsersByMailDomain, ни findUsersByGeo. ·>Почему не буду?
Потому что в stateful OOP реализации невозможно повторно использовать код этих методов. Они возвращают некомбинируемые результаты.
Ну, то есть формально-то они комбинируемые — можно взять два набора Result, и выполнить их пересечение за O(N logM), но на практике это приведёт к неприемлемой производительности.
S>>А вы по факту останетесь без тестов, т.к. N*M вы писать вспотеете, и останетесь с набором зелёных тестов даже в случае косяков в corner cases. ·>Фантазии.
Жизненный опыт.
S>>Так и тут — если уж мы решили провести границу тестирования вокруг произвольно выбранного нами компонента системы — скажем, репозитория, то что нам помешает порезать этот репозиторий на K компонент? ·>Ну "findUsersByMailDomainAndGeo" — это поведение или что? А вот contains("top(10)") — это по-моему совершенно не поведение.
Поведение.
·>Ну так да, уж несколько месяцев объясняю, что код похожий. Тесты другие.
Ну, штош.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>·>Причём тут ширина? S>Ширина при том, что findUsersByMailDomainAndGeo имеет как минимум четыре "особых точки" в зависимости от того, какой из критериев домена или географии установлен в "any".
Ты ж вроде сам расписал как это линеаризуется.
S>Если мы продолжим наворачивать туда критерии (как оно обычно бывает в системах с "настраиваемой отчётностью"), то очень быстро окажется, что у нас нет ресурсов для выполнения e2e по всем-всем мыслимым комбинациям.
Ну не наворачивай.
S>·>Может быть, и тест это проверить никак не может. S>Ну, то есть проблема всё же в дизайне, а не в тестах.
Именно. У ваших тестов совершенно другая проблема.
S>·>Что "это непрактично" и смысла обсуждать не имеет. S>Т.е. и без таких наворотов мы получаем приемлемое качество. Ч.Т.Д.
Угу, не знаю к чему ты завёл речь о системе типов. Это тоже оффтоп.
S>·>Давай определимся, "findUsersByMailDomainAndGeo" — имеет или нет? S>Имеет конечно. У нас в бизнес требованиях указано, что могут быть ограничения на почтовый домен (по точному совпадению, по частичному совпадению) и по географии (на уровне зоны/страны/региона/населённого пункта). Если задано более одного ограничения — применяются все из них.
Ну вот, это и надо тестировать. А факт того, что "применяются все из них" — говорит о том, что никакого взрыва комбинаций тестировать не надо, т.к. код линейный. Достаточно протестировать каждый критерий отдельно.
S>·>Зачем? Сразу после выделения (т.е. рефакторинг прод-кода) — тесты не трогаем, они должны остаться без изменений и быть зелёными. S>Они не могут остаться без изменений; у вас там стратегию нужно замокать. Ну, вот как с датой — перенос её в TimeSource требует от вас в рамках теста подготовить нужный источник времени.
Если существующий тест как-то умудрялся работать без подготовленного источника, то он должен работать ровно так же и с подготовленным.
S>>>Опять же — вот вы приводите пример оптимизации, которая типа ломает addLimit. Ну, так эта оптимизация — она откуда взялась? Просто программисту в голову шибануло "а перепишу-ка я рабочий код, а то давненько у нас на проде ничего не падало"? Нет, пришёл бизнес и сказал "вот в таких условиях ваша система тормозит. Почините". И программист починил — новая реализация покрывает новые требования. S>·>Ну да. Но все старые тесты остаются как есть, зелёными. Создаём новые тесты для новых требований, но не трогаем старые. Когда задача завершена, и очень хочется, то можно проанализировать какие тесты стали избыточными и их просто выкинуть. S>Точно так же, как и в FP-случае.
Именно. ЧТД, дизайн тут непричём.
S>·>Это не избавляет от необходимости тестировать части в сборе. И проблема в том, что тестировать весь конвеер целиком "но ресурсные ограничения есть". Упс. S>Конвеер тестируется в длину, а не в ширину. Для этого достаточно 1 (одного) теста.
Судя по тому что писал Pauel этот самый конвеер есть в каждом методе каждого контроллера. Т.е. таких конвееров туча у вас.
S>·>Всё верно. Суть в том, что надо тестировать не умение субд, не порождение запросов, не доезжание, а что findUsersByMailDomainAndGeo действительно ищет юзеров по домену и гео. А как он это делает, через sql, через cql, через linq или через чёрта лысого — пофиг. S>В теории — да, но на практике "через чёрта лысого" вы просто не покроете тестами никогда.
Возможно, зависит от конкретноого чёрта, вот только моки тут непричём.
S>>>Может быть, find() просто делает return empty(). S>·>Да пускай, пофиг. Зато этот конкретный сценарий — правильный, и он будет всегда правильный, независимо от реализации, даже для return empty, это инвариант — и это круто.
S>>>И даже если вы добавите assert find(vasya) == vasya, это мало что изменит — потому что нет гарантии, что find(vasilisa) != vasya. S>·>Конечно. Но ты забыл, что никакие тесты такую гарантию дать и не могут. Твои тоже. S>Формально — да, не могут. S>На практике мы можем проверить не только статус тестов, но и test coverage. В общем случае по test coverage никаких выводов делать нельзя, т.к. результат зависит от путей исполнения. Но когда мы принудительно линеаризуем исполнение, из code coverage можно делать выводы с высокой степенью надёжности.
Угу. Вот только это зависит от дизайна, стиля, ревью и т.п., а не от наличия моков или подходу к тестированию.
S>Вот в вашем примере про добавление оптимизированной ветки в query — ну и прекрасно, она у нас уедет в buildWhere, и там мы проверим результаты тестами, а code coverage покажет нам, что для новой ветки тестов недостаточно. При этом addLimit останется покрытым, потому что он добавляется в отдельном от ветвления между "основным" и "оптимизированным" вариантами месте.
Именно, ведь наличие addLimit в правильном месте обеспечивается не тестами, а совершенно другими средствами.
S>>>Чтобы нормально покрыть тестами ваш find(), придётся отказаться трактовать его как чёрный ящик. S>·>Нет. Тесты — продукт corner case анализа, документация к коду, а не инструмент доказательства корректности и обеспечения гарантий. S>Хм. А как вы обеспечиваете гарантии?
Ты же сам рассказывал: "от обучения разрабочиков, до код ревью и отбора прав" и т.п.
S>>>Бизнес-аналитик не будет вам писать тесты. Увы. S>·>Не писать, но если ему задать вопрос который описывает given-часть теста — то аналитик ответит какой правильный результат мне прописать в verify-части — contains или notContains. S>А если ему задать вопрос про то, почему должен быть такой результат — он скажет, каким должен быть оператор
Не скажет в общем случае, т.к. оператор это детали реализации и специфично для яп. Про sql "between" он может и не знать и его совершенно может не волновать inlcusive он или exclusive. zero-based, one-based индексация и т.п. в итоге приводит к off-by-one ошибкам.
S>·>Корректный в смысле синтаксис не сломан? Да пофиг, такое действительно не особо важно проверять. S>Синтаксис и семантика. Корректность семантики проверить тестами невозможно.
S>·>Из тыщи фрагментов можно построить 21000 комбинаций. Но только сотня из них — та, которая нужна. S>Ну вот нам достаточно один раз описать способ построения этой комбинации, и в дальнейшем проверять именно его.
Так проблема в том, как, например, проверять опечатки в этом самом описании. Учти, эти опечатки могут быть даже в требованиях. Бизнес-аналист написал "исползуем оператор <" но перепутал левую часть с правой. Ты просто скопипастишь < в прод-код и тест-код — и всё зелёное, ибо works as coded. А вот по внезапно красному тесту "saveDocument(2024); findDocumentsNewerThan(2025) == empty" который очевидно верный — ты сразу увидишь ошибку.
Может для < это ещё просто, но в каких-то более хитрых сценариях, когда разные знаки например, payer/payee, buy/sell, call/put, bid/ask — очень легко заблудиться.
S>·>моки это не про state, а про передачу параметров. Для тестирования репо+бд моки не нужны вообще-то. Моки нужны для экономии ресурсов. S>Эмм, ваша "модельная БД" — это тоже мок. Нет никакой разницы с т.з. архитектуры тестирования, то ли вы пишете мок вручную и хардкодите в него ответы на find(vasya), то ли вы его описываете декларативно с помощью мок-фреймворка, то ли берёте "квазинастоящую БД" и напихиваете данными перед тестом. S>Это всё — про одно и то же "у меня есть stateful dependency, и я моделирую этот state перед каждым тестом".
Так это один-в-одит переводится в ФП без state: val records = [vasya]; find(records, criteria) == vasya. Тут дело не в state, а что именно проверяется тестами.
Ещё раз — моки это лишь механизм передачи данных. Цель тестов с субд не в том, чтобы иметь состояние, а в том, чтобы проверять взаимодействие нашего кода с субд, что все компоненты (запрос, bind-парамы, схема, поля в select, result-set процессоры, етс...) согласованы. Проверять эту согласованность через e2e — очень накладно. Но где-то придётся, в любом случае.
S>·>Что показать? S>Код.
Да такой же как ты показал. Разница только в том, что делается в тестах для тестирования этого кода.
S>·>У вас тоже они где-то есть. S>Они — на самом верху, в конце конвеера, и их достаточно проверить при помощи немногих e2e тестов.
В конце каждого из конвееров.
S>·>Почему не буду? S>Потому что в stateful OOP реализации невозможно повторно использовать код этих методов. Они возвращают некомбинируемые результаты. S>Ну, то есть формально-то они комбинируемые — можно взять два набора Result, и выполнить их пересечение за O(N logM), но на практике это приведёт к неприемлемой производительности.
Причём тут вообще state?
S>>>Так и тут — если уж мы решили провести границу тестирования вокруг произвольно выбранного нами компонента системы — скажем, репозитория, то что нам помешает порезать этот репозиторий на K компонент? S>·>Ну "findUsersByMailDomainAndGeo" — это поведение или что? А вот contains("top(10)") — это по-моему совершенно не поведение. S>Поведение.
А если вместо top(10) реализуешь то же самое через between какой-нибдуь?
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, Pauel, Вы писали:
P>·>Что за бред. Могу, конечно. Вопрос только в том какие я/ты тесты будем писать для findUsersByMailDomainAndGeo. Ты будешь проверять, что findUsersByMailDomainAndGeo внутри себя реализована как комбинация findUsersByMailDomain и findUsersByGeo, что и так явно видно из реализации этого метода. А я напишу тест, что вот для данного набора юзеров функция возвращает нужных и не возвращает ненужных. P>"не возвращает ненужных" — мешает теорема Райса. Все что сложнее одной таблицы уже просто так пальцем на заткнуть.
_Для данного набора_ ведь.
P>·>Поэтому если завтра придётся что-то пооптимизировать в реализации и что-то перекомбинировать как-то по-другому — ты свои тесты выкинешь и напишешь совершенно другие, оставшись без регрессии. P>Если без регрессии то это же хорошо. А вы что, в регрессии пользу видите?
Ну вот я и тут ошибся в двойном отрицании. Очевидно, я тут имел в виду "без регрессионного тестирования".
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали:
P>>"не возвращает ненужных" — мешает теорема Райса. Все что сложнее одной таблицы уже просто так пальцем на заткнуть. ·>_Для данного набора_ ведь.
Из этого утверждения следует, что ваши тесты для конкретного набора не дают основания считать что будет корректная работа на другом наборе данных.
То есть, ваши тесты никакой свойство функции зафиксировать не могут.
А вот статическая типизация как раз таки фиксирует свойство функции. Ровно как и пред-, пост-условия, инварианты. Технически, пост-условие можно выразить синтаксисом. Практически, таких языков не так много в природе.
Здравствуйте, ·, Вы писали:
S>>·>Корректный в смысле синтаксис не сломан? Да пофиг, такое действительно не особо важно проверять. S>>Синтаксис и семантика. ·>Корректность семантики проверить тестами невозможно.
Именно так. Потому ваши утверждения про тесты можно повычеркивать.
Здравствуйте, Pauel, Вы писали:
P>>>"не возвращает ненужных" — мешает теорема Райса. Все что сложнее одной таблицы уже просто так пальцем на заткнуть. P>·>_Для данного набора_ ведь. P>Цитирую одного из тех, кто пишет с вашего аккауна P>
А я не корректность семантики проверяю, а исполнимость конкретного сценария. Возвращает ли данный метод конкретный список записей для конкретных параметров за конкретное время билда или нет — Райсу никак не противоречит.
P>Из этого утверждения следует, что ваши тесты для конкретного набора не дают основания считать что будет корректная работа на другом наборе данных.
И ваши тесты тоже.
P>То есть, ваши тесты никакой свойство функции зафиксировать не могут.
И ваши тесты тоже.
P>А вот статическая типизация как раз таки фиксирует свойство функции. Ровно как и пред-, пост-условия, инварианты. Технически, пост-условие можно выразить синтаксисом. Практически, таких языков не так много в природе.
Похрен. Разговор идёт про тесты.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, Pauel, Вы писали:
S>>>Синтаксис и семантика.
Забыл добавить: а синтакстис и так проверяет компилятор, неясно зачем это проверять ещё и тестами.
P>·>Корректность семантики проверить тестами невозможно. P>Именно так. Потому ваши утверждения про тесты можно повычеркивать.
Какие же именно _мои утверждения_? Цитаты, ты конечно, не сможешь привести.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
·>А я не корректность семантики проверяю, а исполнимость конкретного сценария. Возвращает ли данный метод конкретный список записей для конкретных параметров за конкретное время билда или нет — Райсу никак не противоречит.
Исполнимость конкретного сценария это и есть семантика. Противоречит, еще как.
P>>Из этого утверждения следует, что ваши тесты для конкретного набора не дают основания считать что будет корректная работа на другом наборе данных. ·>И ваши тесты тоже.
Ну так у меня основная работа вне тестов сделана — соответствующий дизайн и вполне осязаемое постусловие
P>>А вот статическая типизация как раз таки фиксирует свойство функции. Ровно как и пред-, пост-условия, инварианты. Технически, пост-условие можно выразить синтаксисом. Практически, таких языков не так много в природе. ·>Похрен. Разговор идёт про тесты.
Здравствуйте, ·, Вы писали:
P>>·>Корректность семантики проверить тестами невозможно. P>>Именно так. Потому ваши утверждения про тесты можно повычеркивать. ·>Какие же именно _мои утверждения_? Цитаты, ты конечно, не сможешь привести.
Смотрите что сами пишете — выполнимость конкретного сценария.
Здравствуйте, Pauel, Вы писали:
P>·>А я не корректность семантики проверяю, а исполнимость конкретного сценария. Возвращает ли данный метод конкретный список записей для конкретных параметров за конкретное время билда или нет — Райсу никак не противоречит. P>Исполнимость конкретного сценария это и есть семантика. Противоречит, еще как.
Нет.
P>>>Из этого утверждения следует, что ваши тесты для конкретного набора не дают основания считать что будет корректная работа на другом наборе данных. P>·>И ваши тесты тоже. P>Ну так у меня основная работа вне тестов сделана — соответствующий дизайн и вполне осязаемое постусловие
И у меня так. Но у меня ещё и тесты неговно.
P>>>А вот статическая типизация как раз таки фиксирует свойство функции. Ровно как и пред-, пост-условия, инварианты. Технически, пост-условие можно выразить синтаксисом. Практически, таких языков не так много в природе. P>·>Похрен. Разговор идёт про тесты. P>Это вы хотите говорить только про тесты.
Потому что ты наезжаешь именно на мои тесты. Цитирую: "что ваши тесты для конкретного набора...".
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, Pauel, Вы писали:
P>>>Именно так. Потому ваши утверждения про тесты можно повычеркивать. P>·>Какие же именно _мои утверждения_? Цитаты, ты конечно, не сможешь привести. P>Смотрите что сами пишете — выполнимость конкретного сценария.
Угу, тебе осталось понять написнное.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали:
P>>>>Именно так. Потому ваши утверждения про тесты можно повычеркивать. P>>·>Какие же именно _мои утверждения_? Цитаты, ты конечно, не сможешь привести. P>>Смотрите что сами пишете — выполнимость конкретного сценария. ·>Угу, тебе осталось понять написнное.
Вот вы пишите: "Возвращает ли данный метод конкретный список записей для конкретных параметров за конкретное время билда или нет"
Видите — здесь вы утверждаете вроде бы верное утверждение — конкретные параметры, конкретный билд и тд. В этом случае противоречия с т. Райса нет.
Если бы вы изначально это повторяли, к вам бы и вопросов не было — тесты показывают исключительно здесь и сейчас. Это их сила и слабость.
А вы идете дальше, и утверждаете, что
1 ваши распрекрасные тесты крутые
2 следовательно, после деплоймента их повторять смысла нет
Собственно, здесь вы сами себе противоречите. Крутость тестов ничего не говорит про их возможное состояние в другой момент запуска, в других условиях.
Потому как семантику они не проверяют, не могут — с этим вы вроде бы согласились.
А раз не могут, то семантика которая делает их красными вполне себе вероятна — сама по себе она никуда не денется.
Вот вам снова обоснование тестам после деплоймента. Причины — тот самый недетерминизм, который гарантированно есть:
другое время,
другая сеть,
другой энвайрмент,
другие данные,
другая нагрузка
итд
хоть бит-перфект соответствие энвайрментов — всё равно есть недерминизм.
Здравствуйте, Pauel, Вы писали:
P>Вот вам снова обоснование тестам после деплоймента. Причины — тот самый недетерминизм, который гарантированно есть:
По недетерминизму я тоже тебе всё объяснял. Его нужно менеджить. Выявлять и бороться.
P>другое время,
Рассказал как до мелочей, с примерами кода. Но ты сам себе стреляешь в ногу отстаивая право засирать весь код DateTime.Now глобальными переменными.
P>другая сеть,
Многопоточка, по сути. В проде её выцепить гораздо сложнее, чем в тестовом енве.
P>другой энвайрмент,
Конфиги? Менеджатся, расказал как.
P>другие данные,
У тестов данные по определению ровно такие же.
P>другая нагрузка
Мониторится.
P>итд P>хоть бит-перфект соответствие энвайрментов — всё равно есть недерминизм.
Исследуем, находим, воюем с недетерминизмом. Тесты для этого — далеко не лучший инструмент.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай