Информация об изменениях

Сообщение Re[94]: Что такое Dependency Rejection от 15.03.2024 12:02

Изменено 15.03.2024 13:04 ·

Re[94]: Что такое Dependency Rejection
Здравствуйте, 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 строк ты сам нафантазировал. Но мы выкатываем продукт где сотни тысяч строк. И мы всё ещё как-то должны не пропускать очепятки.
Re[94]: Что такое Dependency Rejection
Здравствуйте, 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 строк ты сам нафантазировал. Но мы выкатываем продукт где сотни тысяч строк. И мы всё ещё как-то должны не пропускать очепятки.