Re[93]: Что такое Dependency Rejection
От: Pauel Беларусь http://blogs.rsdn.org/ikemefula
Дата: 10.03.24 15:15
Оценка:
Здравствуйте, ·, Вы писали:

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

·>smoke tests — как вишенка на торт, но в остальном — проверять две вещи: части работают, части собираются между собой нужным образом.

Что это за тесты, кто их выполняет?

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


P>>Причин может быть много. Качество не сводится к тестам. Более того — тесты в обеспечении качества это минорная часть.

·>Минорная?! Без тестов обеспечить качество практически невозможно.

Минорная — это значит, что есть вещи более важные
1. методология
2. требования
3. дизайн согласно методологии
4. реализация согласно дизайна
5. квалификация людей в команде

Если у вас нет хотя бы одного 1..4 никакие тесты вам не помогут, хоть обмажтесь.
А вот если есть всё выше, то можете подключать
6. контроль качества
При этом, тесты это всего лишь часть контроля качества

P>>Какая связь e2e и "гонять проверки каждого поля на null" ? e2e это про сценарий, а не прогон по всем комбинациям полей/значений.

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

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

P>>e2e много и не нужно.

·>Где ты предлагаешь проверять поля на null тогда?

Какие именно поля на null ? ui, dto, bl, конфиг, бд?

P>>·>А ещё всякие завязанные на реальное время тесты. Как ты e2e будешь тестировать, что через 30 минут неактивностии юзера происходит логаут? Будешь ждать по пол часа каждый раз?

P>>Как без таких тестов сможете убедиться, что на реальной системе у вас действительно эти 30 минут а не месяцы и годы?
·>Ты и со своими тестами не сможешь убедиться. Ещё раз, это достигается другим способом. Строишь модель физического времени, например как int64 unix-epoch nanoseconds. А дальше в тестах тупо прибавляешь разное количество наносекунд к моку источника времени и смотришь, что логаут не|произошел. Тогда и конкретное количество времени неважно, хоть 30 минут, хоть 30 лет — тест будет выполяться мгновенно в любом случае. И нет никаких проблем прогонять этот тест каждый раз перед каждым релизом.

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

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

P>>Если этот шаг неважный, его в e2e выносить не нужно. А если это важно, то это проверять надо регулярно:

·>Ок... ну может логаут неважный. Но вот например "отослать statement в конце каждого месяца" — шаг ооочень важный. Тоже e2e тест сделаешь со sleep(1 month)?

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

P>> У вас что, лимит на количество тестов, добавили e2e — теперь надо сотню юнит-тестов удалить? Не пойму ваш кейс.

·>Падение e2e-теста означает дыру в нижележащих тестах. Если ваши e2e-тесты падают, чаще чем рак на горе свистит, значит у вас внизу пирамиды дырявые тесты.

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

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

·>Я под интеграционным кодом подразумеваю который вызывает "те ошибки, которые проходят мимо ваших моков". Так вот мимо моков проходят ошибки в доле процента кода, который выполняется только при реальном старте приложения, код в том самом composition root.

Ну да, вы выдумали какое то своё определение интеграционного кода. А ошибки с построением запроса фильтров, они как, по вашему, где ходят?

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


Потому, что покрывать тестами нужно все критические свойства, а не ограничиваться только теми, что в спеке написаны.

P>>Вы, похоже, под интеграционным кодом называете только тот, что в composition root. Контроллер, роутер, юз-кейс — это всё интеграционный код в чистом виде, эти вещи ничего сами не делают. Задача контроллера — связать конкретный вызов от роутера с юзкейсом, валидацией и де/серилизацией. Роутера — конкретный запрос с контроллером. юз-кейс — связать воедино пайплайн для работы БЛ для конкретного запроса.

·>Только тот код, что в composition root сложно покрывается тестами, т.к. там создаются и инжектятся реальные объекты и требует дорогого e2e тестирования. Остальное — неинтересно, ибо быстро, тривиально и надёжно тестируется с помощью моков.
·>Если вы не в сосотянии протестировать контроллер, роутер, юзекйс валидацию и сериализацию без e2e — у вас проблемы.

Успокойтесь, e2e это не для теста контролера. Наверное вы забыли, что я вам предлагал тестировать валидацию юнит-тестами?

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

·>Ты предложил прогонять не все тесты перед выкаткой в прод.

Я вам даже рассказал как именно — все ваши тесты вместе взятые пускаете как сейчас. Допустим их 1000 штук. Вот эту 1000 оставьте и не трогайте.
А вот далее вы добавляете новый вид тестов, которые будут запускаться регулярно после деплоя. Их штук 10, новых.
Итого — 1000 пускаете как раньше, + 10 новых регулярно.

P>>Откуда возьмутся проблемы у юзеров на проде?

·>Не все тесты выполнены.

Похоже, X + 1 у вас может быть меньше X. Забавно!

P>>Ваши имеющиеся тесты отработали — за счет чего регрессия произойдет?

·>Наши да, ваши — не все.

Непонятно — регрессии откуда взяться? Вы добавили новый вид тестов — старые не трогали. Новые предназначены для регулярного запуска после деплоя.
Откуда возьмется регрессия?

P>>Ну и идите. У вас варианты

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

Вот и отлично — значит наличие красных тестов наутро сообщит вам о проблеме еще на стейдже.

P>>Это все так делают.

·>Откуда тогда у тебя берутся красные тесты после деплоя в прод?

1 нагрузка
2 количество и качество юзеров итд
3 новое состояние, которого на тестовом окружении никогда не было
4 инфраструктура
5 внешние системы
6 ошибки, которые были не видны на тестовом окружении, но стали видны на проде, например, эффект накопления
7 редкие ошибки — прод тупо работает на порядки дольше тестового окружения

P>>И никто, кроме лично вас не утверждает "на проде багов быть не может"

·>Ты меня заебал. Не пиши свои бредовые фантазии как мои цитаты. Я такого никогда нигде не утверждал.

Смотрите, вот ваши цитата выше:
"Те, для которых написаны тесты — ясен пень не доходят(до прода)" — в скобках это контекст обозначил

Есть бага. Ну написали вы тест, два, десять, сто. И пофиксили багу. Зеленые тесты.
Но бага это не про фикс, а про поведение. А теперь см п 1..7 выше, каждый из этих пунктов спокойно может воскресить вашу багу.
А вы утверждаете, что раз есть тест, то такой баги на проде точно нет

P>>Вы из своих функций вызываете другие свои функции? Вот вам и интеграционный код. Посмотрите, сколько у вас такого.

·>Непокрытого лёгкими тестами? Полторы строчки, всё верно.

Сюда нужно вписать data complexity. Хороший пример — те самые фильтры.

P>>·>Т.е. у тебя есть ровно те же "репозиторий + бд" тесты?

P>>Есть конечно же. Только таких тестов немного — они дают слабые гарантии в силу своей косвенности.
·>Слабые относительно чего?

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

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

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

Как это будет с linq вам Синклер рядом показал. Сама задача в том, что фильтровать нужно по выражению от юзера, который а хрен знает что может понавыбирать.

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

·>Потому что попытка юзера выполнить функцию проваливается из-за ошибок в синхронизации.

Необязательно, перестаньте фантазировать.

P>>А до того что вы ассертить будете, что бы исключить такую проблему на проде?

·>Ещё раз, эта проблема ассертами в тестах не решается.

В том то и дело. А что у вас есть кроме тестов?

P>>А дальше нам нужно

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

Что бы ассертить число записей нужно знать комбинацию которой у вас точно нет. Можно реализовать ленивую подгрузку. Это вобщем тот же вариант, вид сбоку — юнит-тестом вы можете зафиксировать именно такую особенность структуры.

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

P>>2. паттерн который соответствует дизайну
P>>3. код ревью
P>>4. тесты, которые фиксируют сохранность 1 и 2
P>>Вы из 1..4 видите только тесты, а всё остальное для вас пустое место.
·>Не потому что пустое место, а потому что мы тут обсуждаем тесты и разные подходы к тестированию, а 1..3 — оффтопик. Но ты всё не успокоишься и продолжаешь словоблудить.

Никакой это не оффтопик. пп 1 2 и 3 говорят о том, что можно изменить дизайн, и ваши моки будут больше не нужны.

P>>Каким образом .top(10) пропустит десятки миллионов записей? подробнее.

·>Поставлен не туда или поставлен условно.

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

P>>Наоборот. Потерять пост-условие в коде всегда легче легкого.

P>>Для того и нужны код ревью и тесты, что бы сохранить свойства кода
·>_такой_ тест не нужен. Нужен другой тест.

Другой, которого вы так и не показали?

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

P>>Цитирую себя
P>>Вы из 1..4 видите только тесты, а всё остальное для вас пустое место.
·>Т.е. твои тесты — проверяют меньше, чем мои. Мои, помимо того, что кол-во записей ограничивается 10ю так же протестирует и валидность запросов, подстановку парамов и т.п.

Мои тесты фиксируют структуру решения. Ваши без конкретного перечня комбинаций ничего не гарантируют.

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

·>Ты о чём? Что за исключённые тесты?

Закоментили, удалили, исправили ожидания. Если вы видите, что тест на .top убрали, надо пойти и дать по шее.

P>>acceptance в минутах считается только для hello world.

·>Завидуешь, да? Ну вообще говоря acceptance гоняется на кластере, вот и укладывается в пол часа.

Полчаса это десятки минут. e2e это вобщем тот же acceptance. Если у вас есть такое, чего же вы здесь выступаете?

P>>Непонятно, что за кунсткамера — запрос byId а на пустой список аргументов вгружается вся база?

·>Ну можно придумать что, например, для пустого списка cгенерится пустой фильтр.

P>>Теорема Райса не про код, а про семантику. Содержит инструкцию X — такое успешно решает любой компилятор.

P>>А вот "выполнится ли инструкция X" — вот такое сводится к проблеме останова.
·>В тесте же конкретный сценарий — запускаем код, дождались завершения, проверяем, выполнилась ли инструкция X. Никакой проблемы останова.

Вы успешно опровергли теорему Райса

P>>Вот-вот. Вы путаете синтаксис и семантику. Любой компилятор справляется и с синтаксисом, и со структурой, но не с семантикой.

·>Так ты зачем-то синтаксис и проверяешь "есть ли в коде .top(10)".

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

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

·>Если есть зрелая система автоматических тестов, это значит, что можно делая exploratory testing (это видимо что ты тут имеешь в виду) описывать новые сценарии, пользуясь уже готовым test harness. Тут же запускать их, смотреть на поведение системы и, может быть, даже закоммитить результат как новый автотест.

exploratory testing это
1. построение нового кейса-сценария-итд — это человек в уме делает, здесь он ограничен только воображением
2. запись шагов используя готовый набор инструментов

п1 вы можете переложить разве что на AI. До недавних дней это делал исключительно человек.

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


Её можно исключить методами родственными знакомой вам статической типизации. Те самые предусловия и соответствующий дизайн.

P>>Там, где у автоматических тестов начинаются недостатки, у других методов будут преимущества.

·>Если ты под другими методами имеешь в виду ручные тесты, то у меня для тебя плохие новости.

Ручные методы это в т.ч. exploratory, и вообще исследование.

·>Главное пока умалчиваешь: тест как выглядит для этого кода? Ну где там deep.eq, pattern или что?


Вам примера Синклера недостаточно, вы хотите у меня тож самое спросить?
const request = create(
    User, 
    u => u.many(q => q.filter(f => f(filterExpression))
                      .orderBy(o => u('created'))
                  .top(10)
    )
);

expect(builder({ параметры })).to.deep.eq(request)


вот так мы можем проверить, добавляет ли билдер top или нет. Билдер будет примерно как у Синклера.

P>>Ищите по огромным синим буквам.

·>Там нет ответа на этот вопрос. Там сказано "далее ...бд минимально заполненой", а что было с бд до "далее" — ты надёжно скрываешь.

Забавно, вы вырезали часть цитаты, и не знаете, что было в той части, что вы же и выбросили? Ищите — я для вас синим подсветил

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


Пример у вас есть?

P>>Вам нужно знание о том, как устроены четные и нечетные — у одних младший бит 1, у других — младший бит 0

·>Отлично. Вот ты и выявил какие комбинации будут проблемными. Придумать входные парамы и ожидания с вариациями младшего бита. Т.е. и тестировать надо именно на комбинациях младшего бита.

Комбинации как раз не выявлены. Только свойство построения чисел

P>>В переводе на фильтры — у запроса всегда есть лимит.

·>"у запроса всегда есть лимит" в переводе в твоём случае будет "функция isOdd всегда использует битовую операцию взятия младшего бита".

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