Re[97]: Что такое Dependency Rejection
От: Sinclair Россия https://github.com/evilguest/
Дата: 18.03.24 02:27
Оценка:
Здравствуйте, ·, Вы писали:

·>Ну например: "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)") — это по-моему совершенно не поведение.
Поведение.

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

Ну, штош.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.