Здравствуйте, ·, Вы писали:
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 строк ты сам нафантазировал.
Ну, вы покажите код вашего репозитория с поиском по сложным критериям — и посмотрим. Может у вас там под капотом ровно то же самое