Отчасти да - если знать про mapped-адреса, то "::ffff:192.0.2.5" просто добавляется в юнит-кейсы, и после инцидента именно так и делается, это правильный регрессионный тест. Вопрос в том, откуда этот кейс возьмется в списке до инцидента.
Юнит-тест проверяет функцию против нашего представления о входах - что придет строка с чем угодно - да, но "что угодно" в тестах всегда конечный список, который мы сами и придумали. Если про mapped-форму не знаешь, ее в списке не будет, и тест честно зеленый. А интеграционный через сокет проверяет само представление: он приносит в проверку тот вход, который реально отдает ядро, без нашего участия. Баг ведь не в функции сравнения - она корректно сравнивает то, что дали. Баг на стыке "что отдает сокет" и "что ожидает проверка", а стыки юнитами не проверяются по определению, на то они и юниты.
Код отрабатывает штатно, тест на «адрес не из сети — отказать» проходит, ревью проходит тоже.
Есть небольшой опыт в тестировании, это классика: проверку вайтлиста тестируют как чистую функцию - подали строку "192.0.2.5", получили allow, подали "10.0.0.1" - deny, все зеленое. А в проде адрес приходит не строкой из теста, а из сокета, и баг живет ровно на этом стыке. Юнит-тестом его не поймать в принципе, хоть 100% покрытия сделай.
Ловится только интеграционным тестом через реальный сокет: поднимаем сервис на "::", подключаемся по 127.0.0.1 и проверяем, что вайтлист со 127.0.0.1 пропускает. Один такой тест в CI - и вся эта история падает на пулл-реквесте с переездом на dual-stack, а не на заявке от клиента. Но так почти никто не делает: проверка адреса выглядит слишком простой, чтобы гонять ее не юнитом.
Статья в целом неплохая, спасибо, хотя по большей части это пересказ уже известных вещей про keyset-пагинацию. Но в коде есть моменты, которые меня смутили. Может я ошибаюсь, поправьте.
В финальном Playwright-тесте по-моему гонка (и это в тесте про гонки). oldResponseSent резолвится сразу после route.fulfill(), то есть ответ браузеру ушел, но приложение его еще не распарсило и DOM не обновило. Негативный toHaveText в этот момент пройдет мгновенно, а баговый append прилетит тактом позже. Тест зеленый, приложение сломано. Я бы вместо проверки "ничего не изменилось" повесил какой-то детерминированный сигнал, хоть счетчик примененных страниц.
В loadAllOperations сортировка проверяется внутри страницы, но не на стыке. Если между последним элементом страницы N и первым N+1 инверсия без дубля, тест это не увидит. При том что весь следующий раздел как раз про нестабильный порядок. Лечится одним ассертом в цикле.
В чеклисте нет серверной валидации курсора. Вы цитируете AIP-158, где сказано отвечать ошибкой при смене параметров, но теста на 400 нет ни одного. А ведь инцидент из начала статьи (status=FAILED с чужим курсором) нормальный API просто бы отклонил, и все фронтовые защиты не понадобились бы.
Ну и про точность курсора ничего нет. В постгресе timestamptz с микросекундами, в JS дата с миллисекундами. Строгое < по округленному значению это готовый пропуск или дубль ровно на границе страницы. Тут напрашивается юнит-тест на encode/decode курсора туда-обратно, а такого уровня в вашей таблице вообще нет.
Тема нужная. Просто раз уж туториал, такие вещи стоило бы поправить.
Надеюсь я правильно понял вопрос! parent действительно один — он задаёт только порядок вычисления, кто раньше. А вот само условие смотрит на сколько угодно полей сразу:
Ветки проверяются сверху вниз, первая подошедшая и срабатывает — так что комбинации возраст + пол + страна + дата разбираются как обычная лесенка условий. Подробности в разделе про условия, а если развилка идёт по одному полю, но с долями внутри — есть <switch> и <mix>.
А когда значение не выбирается, а считается из нескольких других — это <compute>: он читает любое количество полей и выводит из них результат.
Отдельная история — когда выбирать надо не значение, а целую запись из справочника. Для этого есть <pool>, и у него свой фильтр, тоже с составными условиями: filter="spec == Needs && room > 120" — то есть круг кандидатов сужается сразу по нескольким полям строки. Как раз про это будет следующая статья, там разберу подробно.
Дальше, чем кажется. Есть <compute> — считает из уже готовых значений, на нём держатся контрольные суммы вроде Луна и IBAN. Есть <gen type="formula"... — любые расчёты прямо в конфиге, с полусотней функций. И есть prev() — ссылка на предыдущую строку/генерацию, отсюда берутся последовательности и накопления:
А если своя логика всё-таки нужна — её можно вынести наружу через <gen type="http"...: ваш сервис остаётся вашим кодом, движок просто к нему обращается.
Да, это генератор тестовых данных, всё верно. Статья первая из цикла, вводная — я в ней просто показал на простом примере, как оно работает.
Про сам инструмент написал только в конце. Побоялся, что иначе получится самореклама, а Хабр её не жалует. Похоже, перестарался: раз вопрос возник первым же.
Про валидаторы вы правы, всё так. Только валидатор ничего не создаёт — он умеет сказать «нет». А данные для тестов надо откуда-то взять, и желательно сразу непротиворечивые. Бывает нужно и обратное: намеренно сломанные, чтобы проверить сам валидатор.
А про ветвление вы прямо в точку, в статье ровно оно и есть. Ваши примеры с ИНН и ДУЛ, кстати, нагляднее моих диагнозов.
Информация
В рейтинге
646-й
Дата рождения
Зарегистрирован
Активность
Специализация
Инженер по автоматизации тестирования, Инженер по производительности
Отчасти да - если знать про mapped-адреса, то "::ffff:192.0.2.5" просто добавляется в юнит-кейсы, и после инцидента именно так и делается, это правильный регрессионный тест. Вопрос в том, откуда этот кейс возьмется в списке до инцидента.
Юнит-тест проверяет функцию против нашего представления о входах - что придет строка с чем угодно - да, но "что угодно" в тестах всегда конечный список, который мы сами и придумали. Если про mapped-форму не знаешь, ее в списке не будет, и тест честно зеленый. А интеграционный через сокет проверяет само представление: он приносит в проверку тот вход, который реально отдает ядро, без нашего участия. Баг ведь не в функции сравнения - она корректно сравнивает то, что дали. Баг на стыке "что отдает сокет" и "что ожидает проверка", а стыки юнитами не проверяются по определению, на то они и юниты.
Хороший разбор, взгляд зацепился:
Есть небольшой опыт в тестировании, это классика: проверку вайтлиста тестируют как чистую функцию - подали строку "192.0.2.5", получили allow, подали "10.0.0.1" - deny, все зеленое. А в проде адрес приходит не строкой из теста, а из сокета, и баг живет ровно на этом стыке. Юнит-тестом его не поймать в принципе, хоть 100% покрытия сделай.
Ловится только интеграционным тестом через реальный сокет: поднимаем сервис на "::", подключаемся по 127.0.0.1 и проверяем, что вайтлист со 127.0.0.1 пропускает. Один такой тест в CI - и вся эта история падает на пулл-реквесте с переездом на dual-stack, а не на заявке от клиента. Но так почти никто не делает: проверка адреса выглядит слишком простой, чтобы гонять ее не юнитом.
А при чём тут СУБД? Это про генерацию тестовых данных. Очень жаль, что вы не потрудились ознакомиться с текстом до комментария.
Статья в целом неплохая, спасибо, хотя по большей части это пересказ уже известных вещей про keyset-пагинацию. Но в коде есть моменты, которые меня смутили. Может я ошибаюсь, поправьте.
В финальном Playwright-тесте по-моему гонка (и это в тесте про гонки). oldResponseSent резолвится сразу после route.fulfill(), то есть ответ браузеру ушел, но приложение его еще не распарсило и DOM не обновило. Негативный toHaveText в этот момент пройдет мгновенно, а баговый append прилетит тактом позже. Тест зеленый, приложение сломано. Я бы вместо проверки "ничего не изменилось" повесил какой-то детерминированный сигнал, хоть счетчик примененных страниц.
В loadAllOperations сортировка проверяется внутри страницы, но не на стыке. Если между последним элементом страницы N и первым N+1 инверсия без дубля, тест это не увидит. При том что весь следующий раздел как раз про нестабильный порядок. Лечится одним ассертом в цикле.
В чеклисте нет серверной валидации курсора. Вы цитируете AIP-158, где сказано отвечать ошибкой при смене параметров, но теста на 400 нет ни одного. А ведь инцидент из начала статьи (status=FAILED с чужим курсором) нормальный API просто бы отклонил, и все фронтовые защиты не понадобились бы.
Ну и про точность курсора ничего нет. В постгресе timestamptz с микросекундами, в JS дата с миллисекундами. Строгое < по округленному значению это готовый пропуск или дубль ровно на границе страницы. Тут напрашивается юнит-тест на encode/decode курсора туда-обратно, а такого уровня в вашей таблице вообще нет.
Тема нужная. Просто раз уж туториал, такие вещи стоило бы поправить.
Надеюсь я правильно понял вопрос!
parentдействительно один — он задаёт только порядок вычисления, кто раньше. А вот само условие смотрит на сколько угодно полей сразу:Ветки проверяются сверху вниз, первая подошедшая и срабатывает — так что комбинации возраст + пол + страна + дата разбираются как обычная лесенка условий. Подробности в разделе про условия, а если развилка идёт по одному полю, но с долями внутри — есть
<switch>и<mix>.А когда значение не выбирается, а считается из нескольких других — это
<compute>: он читает любое количество полей и выводит из них результат.Отдельная история — когда выбирать надо не значение, а целую запись из справочника. Для этого есть
<pool>, и у него свой фильтр, тоже с составными условиями:filter="spec == Needs && room > 120"— то есть круг кандидатов сужается сразу по нескольким полям строки. Как раз про это будет следующая статья, там разберу подробно.Дальше, чем кажется. Есть
<compute>— считает из уже готовых значений, на нём держатся контрольные суммы вроде Луна и IBAN. Есть<gen type="formula"...— любые расчёты прямо в конфиге, с полусотней функций. И естьprev()— ссылка на предыдущую строку/генерацию, отсюда берутся последовательности и накопления:А если своя логика всё-таки нужна — её можно вынести наружу через
<gen type="http"...: ваш сервис остаётся вашим кодом, движок просто к нему обращается.Спасибо, что поделились своим опытом!
Да, это генератор тестовых данных, всё верно. Статья первая из цикла, вводная — я в ней просто показал на простом примере, как оно работает.
Про сам инструмент написал только в конце. Побоялся, что иначе получится самореклама, а Хабр её не жалует. Похоже, перестарался: раз вопрос возник первым же.
Про валидаторы вы правы, всё так. Только валидатор ничего не создаёт — он умеет сказать «нет». А данные для тестов надо откуда-то взять, и желательно сразу непротиворечивые. Бывает нужно и обратное: намеренно сломанные, чтобы проверить сам валидатор.
А про ветвление вы прямо в точку, в статье ровно оно и есть. Ваши примеры с ИНН и ДУЛ, кстати, нагляднее моих диагнозов.