Pull to refresh

Comments 4

Принцип «известного ответа» хорошо переносится на тестирование API. Если заранее положить в базу данные, для которых сумма, количество или статус известны точно, проверка ответа превращается из «выглядит правдоподобно» в сравнение с конкретным числом.

Самое трудное на практике — держать ожидаемое значение в актуальном состоянии, когда меняется генерация. У вас ожидаемый ответ выводится из конфига автоматически или его приходится пересчитывать руками?

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

Если вы о том, не выдаст ли следующая генерация совсем другие данные

Не выдаст. Данные решает пара «конфиг + зерно» (seed). С тем же конфигом и тем же зерном получается тот же файл байт в байт: сколько ни запускай, на любой машине и на любом из пяти языков, на которых написан движок (TypeScript, Python, Rust, Java, C#). Сами по себе данные не мутируют, поэтому однажды посчитанное ожидание остаётся верным.

Ожидание меняется только в двух случаях, и оба зависят от вас: вы поменяли конфиг или зерно. Нужен другой набор данных, устроенный по тем же правилам, - меняете зерно ключом --seed, конфиг не трогаете.

Если вы о том, откуда брать ожидаемое число

Его считает сам конфиг, руками ничего пересчитывать не нужно. Генератор stat берёт уже сгенерированную колонку и считает по ней одно число на весь запуск: сумму, количество, среднее, медиану, минимум, максимум или стандартное отклонение. Это число можно напечатать в тот же файл, рядом с данными, через фикстуру <after>:

<tdc>
  <env count="1000" seed="api">
    <after><line><data>-- expected: paid_count=${{PaidCount}} paid_sum=${{PaidSum}}</data></line></after>
    <sequence name="Id"><gen type="increment" value="1"/></sequence>
    <sequence name="Status"><gen type="text" value="paid,refunded,pending" percent="70,20,10"/></sequence>
    <sequence name="Amount"><gen type="number" value="100..5000" decimals="2"/></sequence>
    <sequence name="Paid"><gen if="Status == 'paid'" type="formula" expr="Amount"/></sequence>
    <sequence name="PaidSum"><gen type="stat" of="Paid" op="sum" decimals="2"/></sequence>
    <sequence name="PaidCount"><gen type="stat" of="Paid" op="count"/></sequence>
  </env>
  <block>
    <line><data>INSERT INTO orders VALUES (${{Id}}, '${{Status}}', ${{Amount}});</data></line>
  </block>
</tdc>

Файл заканчивается строкой:

-- expected: paid_count=700 paid_sum=1789472.77

Тест читает ожидание из этой строки, а не держит число у себя в коде. Поменяли генерацию — следующий запуск пересчитал и данные, и ожидание вместе с ними. Количество к тому же известно заранее: percent="70,20,10" даёт ровно 700 оплаченных заказов из 1000, а не «около 700».

Если вы о том, как не дать ожиданию устареть незаметно

Для этого есть <assert>: ожидание записывается прямо в конфиг.

<assert that="PaidCount == 700" says="оплаченных ровно 700"/>

Если кто-то поменяет генерацию так, что ожидание перестанет выполняться, запуск остановится с объяснением, а не выдаст файл с молча другим ответом. Например, кто-то поднял объём до 2000:

tdcv2: assert failed: оплаченных ровно 700
  PaidCount == 700   with PaidCount = 1400

А поскольку число меняется только вместе с конфигом, его правка видна в диффе пулл-реквеста рядом с правкой теста.

Если пересчитывать не хочется вовсе

Есть tdcv2-agents — skill и MCP-сервер для Claude Code, Codex, Cursor и других агентов (как подключить). Агенту можно сказать: «добавь в конфиг ожидаемые суммы по статусам и проверку на них», и он сам допишет stat и assert, а потом прогонит конфиг движком. Оговорка: из-за новых правил Anthropic для плагинов (с 25 октября 2026) я сейчас прохожу верификацию, поэтому плагин пока собран под предыдущую версию движка. Работать это не мешает - агент сверяется с актуальной документацией.

Если вы имели в виду что-то другое, опишите, пожалуйста, ситуацию подробнее: что за API, что лежит в базе и что именно проверяет тест. Постараюсь подсказать, как это сделать.

Спасибо, вы ответили как раз на второй вариант. Строка с ожидаемым значением в конце файла, которую тест читает сам, снимает вопрос, держать ли число в коде. А assert в конфиге ловит сломанное ожидание ещё при генерации, а не в упавшем тесте. Уточню одно: сохраняется ли «байт в байт» между версиями движка, если обновить сам tdcv2 при том же конфиге и зерне?

Хороший вопрос, внутри одной версии - да, между версиями - пока не гарантируется.

Внутри одной версии "байт в байт" - правило движка: тот же конфиг и то же зерно дают тот же файл на любой машине и в любом из пяти языков. Его проверяют общие тесты на каждом изменении, а расхождение между языками считается ошибкой и исправляется.

Между версиями гарантии сейчас нет, и номер версии об этом честно говорит. Пока он начинается с нуля (сейчас 0.4.0), по правилам семантического версионирования проект ещё стабилизируется. Большинство выпусков вывод существующих конфигов не трогают. Но если выпуск исправляет ошибку, а старый вывод был неверным, байты в этом месте меняются. Например, в 0.1.7 доли внутри <switch> стали точными: на 200 000 строк вместо 44 999 записей стало ровно 45 000. Каждое такое изменение записано в журнале изменений и в разделе «Что нового» с указанием, каких конфигов оно касается.

Что это значит на практике:

  • Если ожидание печатается в тот же файл через stat и <after>, как в примере выше, обновление движка ничего не ломает: данные и ожидание пересчитываются вместе и остаются согласованными.

  • Если вы храните сгенерированный файл как эталон ("золотой" файл) и сравниваете с ним побайтно, зафиксируйте точную версию. Обновляйтесь осознанно: прочитали "Что нового", перегенерировали эталон одним запуском, посмотрели дифф.

Как зафиксировать версию в каждом из пяти реестров:

  • npm (TypeScript, JavaScript): npm install -D tdcv2@0.4.0 --save-exact; разовый запуск - npx tdcv2@0.4.0 config.tdc

  • PyPI (Python): tdcv2==0.4.0 в requirements.txt или pip install tdcv2==0.4.0

  • crates.io (Rust): tdcv2 = "=0.4.0" в Cargo.toml (со знаком =, иначе Cargo сам подтянет 0.4.1); командная строка - cargo install tdcv2 --version 0.4.0

  • Maven Central (Java): <version>0.4.0</version> в Maven или implementation("io.github.nickliapin:tdcv2:0.4.0") в Gradle; командная строка - отдельный jar с версией в имени, tdcv2-0.4.0-cli.jar

  • NuGet (C#): <PackageReference Include="Tdcv2" Version="[0.4.0]" /> (в квадратных скобках, иначе это «0.4.0 или новее»); командная строка - dotnet tool install --global Tdcv2.Cli --version 0.4.0

С версии 1.0.0 вывод будет стабильным: менять его сможет только новая первая цифра версии.

Если найдёте ошибку - что-то считается не так, два языка дают разные файлы, проверка пропустила явную ерунду или сообщение об ошибке ничего не объясняет, - заведите, пожалуйста, issue на GitHub. Лучше всего приложить конфиг, зерно, версию (tdcv2 --version), язык, из которого запускали, и что ожидали получить. Я читаю каждое issue и исправляю найденное в ближайшем выпуске - сразу во всех пяти языках, с тестом, чтобы ошибка не вернулась. Проект открытый, и каждое такое сообщение делает его лучше для всех.

Sign up to leave a comment.

Articles