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.tdcPyPI (Python):
tdcv2==0.4.0вrequirements.txtилиpip install tdcv2==0.4.0crates.io (Rust):
tdcv2 = "=0.4.0"вCargo.toml(со знаком=, иначе Cargo сам подтянет 0.4.1); командная строка -cargo install tdcv2 --version 0.4.0Maven Central (Java):
<version>0.4.0</version>в Maven илиimplementation("io.github.nickliapin:tdcv2:0.4.0")в Gradle; командная строка - отдельный jar с версией в имени,tdcv2-0.4.0-cli.jarNuGet (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 и исправляю найденное в ближайшем выпуске - сразу во всех пяти языках, с тестом, чтобы ошибка не вернулась. Проект открытый, и каждое такое сообщение делает его лучше для всех.
Данные без противоречий: синтетика с известным ответом