Перейти к основному содержимому

TDC — The Data Constructor

TDC генерирует тестовые данные, согласованные внутри записи. В пределах одной строки имена соответствуют полу, города стоят внутри своих стран, а диагнозы подходят профилю пациента. Запустите TDC снова с тем же сидом и той же версией ядра — и он выдаст те же строки, байт в байт.

Обычная библиотека фейковых данных генерирует каждое поле независимо. Из этого различия следует всё остальное.

Чем плохи независимые поля

Когда поля генерируются независимо, каждое значение по отдельности может быть корректным, а запись, которую они составляют, — некорректной целиком. На практике это даёт несколько типов проблем:

  • Сгенерированная пациентка — женщина 34 лет, но ей выставлена «доброкачественная гиперплазия предстательной железы»: диагноз противоречит демографическим данным в той же записи. Падение выглядит как баг приложения, пока кто-нибудь не заглянет в фикстуру.
  • Генератор с фиксированным сидом создаёт миллион заказов, соединяя города и страны случайно. Валидатор адресов отвергает треть из них, и нагрузочный тест меряет ветку с ошибкой вместо самой функции.
  • Тест падает в CI. Вы перезапускаете — генератор выдаёт другие данные, и тест проходит. Понять, починен ли баг на самом деле, невозможно.

У независимо сгенерированных полей нет общего контекста.

Одни и те же три источника, соединённые двумя разными способами.
  • Aтри источника, в каждом лежат значения всех групп
  • Bчто из этого получается: каждое поле тянулось само по себе, и в строке ничто ни с чем не сходится
  • Cзапись начинается с одного источника
  • Dкаждое следующее поле тянется из того, что выбрало предыдущее, — и строка согласуется сама с собой

Как TDC решает эту задачу

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

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

Числа — размеры английских медицинских списков.
  • Aодна строящаяся запись
  • Bветка, в которую она попала
  • Cсписок, доступный только этой ветке: 26 состояний, специфичных для женщин, 20 — для мужчин
  • Dсписок, общий для обеих веток: 78 состояний, которые могут быть у любого
  • Eребро, которого не может быть: запись не выходит за пределы своей ветки

Всё остальное в этой документации построено на этом механизме.

Это не XML

Прочитайте следующий пример раньше этой фразы — и решите, что перед вами XML. Это не он. TDC — самостоятельный формат. Он взял форму угловых скобок, потому что она удобно читается для вложенных именованных вещей, и на этом сходство заканчивается. Файл .tdc не читает ни один XML-парсер, и ни одно правило XML к нему не применяется.

Чего ждут от XML и чего здесь нет:

В XMLВ TDC
сущности — &lt; превращается в <ничего не разворачивается. &lt; — это четыре символа. Пишите <
пространства имён, xmlns:такого понятия нет
DTD или XSD для проверкипроверяет сам движок, по своим правилам
<![CDATA[…]]>не нужно: <data> и так хранит сырой текст
<?xml …?>, <!DOCTYPE …>не принимаются
значение атрибута — просто текстзначение атрибута — выражение TDC: if="Age >= 18" разбирается и вычисляется

Символы < и > внутри <data> — обычные символы и обычными остаются. Именно это позволяет конфигу выдавать JSON, SQL или HTML, не воюя со слоем экранирования.

Почему в примерах написано xml

Блоки кода на сайте помечены xml, чтобы браузер подсветил теги и атрибуты. Этим всё и исчерпывается — это догадка подсветчика синтаксиса, а не утверждение о формате.

Простой пример

Следующая конфигурация генерирует десять человек с разбивкой по полу 60/40. Имена берутся из списков, соответствующих полу, а возраст попадает в заданный диапазон:

people.tdc
<tdc>
<env count="10" seed="demo">
<sequence name="Gender">
<gen type="text" value="Male,Female" percent="60,40"/>
</sequence>

<sequence name="MaleName" parent="Gender.Male">
<gen type="template" value="person.male.firstName"/>
</sequence>
<sequence name="FemaleName" parent="Gender.Female">
<gen type="template" value="person.female.firstName"/>
</sequence>

<sequence name="Age">
<gen type="number" value="18..65"/>
</sequence>
</env>

<block>
<line>
<data>${{_count}}. ${{Gender}} — ${{MaleName}}${{FemaleName}}, age ${{Age}}</data>
</line>
</block>
</tdc>
./run people.tdc
1. Male — Robert, age 59
2. Female — Mary, age 18
3. Male — James, age 53
4. Male — John, age 24
5. Male — Michael, age 28
6. Male — David, age 34
7. Female — Elizabeth, age 57
8. Female — Jennifer, age 58
9. Female — Patricia, age 52
10. Male — William, age 56

В этом выводе стоит отметить три свойства.

Точное распределение. percent="60,40" даёт шестерых мужчин и четырёх женщин. Это не приближение, полученное независимыми случайными бросками: размеры групп TDC считает методом Гамильтона.

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

Воспроизводимый вывод. Тот же сид и та же версия ядра дают тех же десятерых. Другой сид даёт другую десятку, сохраняя разбивку шесть к четырём.

Секция <block> управляет форматом вывода. <line> задаёт строку, <data> — её содержимое. Меняя эту секцию, те же записи можно отрисовать как CSV, JSON, SQL или другой формат.

Зависимости вкладываются друг в друга

Допустим, у половины мужчин машины нет, а у четверти женщин — есть. Для этого нужны две дополнительные последовательности; остальная конфигурация не меняется:

<sequence name="MaleCar" parent="Gender.Male">
<gen type="text" value="has a car,no car" percent="50,50"/>
</sequence>
<sequence name="FemaleCar" parent="Gender.Female">
<gen type="text" value="has a car,no car" percent="75,25"/>
</sequence>
./run people.tdc
1. Male — Robert — has a car
2. Female — Mary — no car
3. Male — James — no car
4. Male — John — has a car
5. Male — Michael — has a car
6. Male — David — no car
7. Female — Elizabeth — has a car
8. Female — Jennifer — has a car
9. Female — Patricia — has a car
10. Male — William — no car

Каждый процент применяется внутри своей родительской группы. В этом примере машины нет у 3 из 6 мужчин и у 1 из 4 женщин. Зависимости можно вкладывать на любую глубину, какой требует модель данных.

Примеры вывода иллюстративны

Для заданного сида и версии ядра TDC выдаёт детерминированный вывод. Движок ещё развивается, поэтому имена и числа, которые выдаёт текущая версия, могут отличаться от показанных здесь.

Важно поведение — в этом примере точная разбивка 60/40, — а не побайтовое совпадение с выводом выше.

Что TDC умеет

  • Детерминированное распределение. percent="60,40" считает размеры групп в целых строках методом Гамильтона, а не полагается на независимые случайные броски.

  • Иерархические зависимости. Поле может зависеть от значения родителя, а зависимости — вкладываться на любую нужную глубину.

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

  • Состав действующих лиц, готовый до строк. <pool> один раз строит маленький мир — клиники, затем врачи, каждый в одной из них, — и строка берёт целого его участника. Поэтому этот врач в каждой упоминающей его строке работает в той же клинике. А filter= держит вместе одну строку: медсестра этого пациента работает там же, где его врач.

  • Уникальные значения. Значения можно генерировать без повторов внутри колонки.

  • Внешние источники данных. Отдельные значения или целые связанные строки можно читать из ваших собственных источников.

  • Гибкие форматы вывода. CSV, JSON, SQL, YAML или ваш собственный формат.

  • Большие наборы. Миллионы строк потоком, без удержания всего набора в памяти.

  • Одно значение без конфига. tdc.person.lastName() — работа, которую делает faker, из тех же паков, откуда черпает конфиг. Есть во всех пяти реализациях, и при одном сиде каждая выдаёт одно и то же значение.

  • Пакеты локалей и стран. Данные о людях, местах, медицинских записях и документах на десяти языках. Пакеты стран поддерживают форматы национальных идентификаторов более чем для девяноста стран — с правилом контрольной цифры, подходящим каждому формату.

Где TDC применяют

  • Автоматизация тестирования. Готовить фикстуры, согласованные внутри записи, и указывать сид в баг-репорте, чтобы воспроизвести ровно тот набор данных, на котором тест упал. TDC доступен и как библиотека, и как консольная утилита, поэтому тесты могут получать сгенерированные строки напрямую, а не полагаться на файлы фикстур, которые надо поддерживать в актуальном состоянии:
import { test, expect } from '@playwright/test';
import { TDC } from 'tdcv2';

const users = new TDC({ configFile: 'users.tdc' }).toArray();

for (const user of users) {
test(`sign up ${String(user.Name)}`, async ({ page }) => {
await page.goto('/signup');
await page.fill('#name', String(user.Name));
await page.fill('#age', String(user.Age));
await page.click('#submit');
await expect(page.getByText('Welcome')).toBeVisible();
});
}
  • Нагрузочное тестирование. Вывод идёт потоком, а не держится в памяти целиком, так что большим наборам не нужно помещаться в RAM.

  • Разработка. Демостенды, песочницы и скрипты наполнения с согласованными данными, которые воспроизводятся в точности.

  • Исследования и работа с данными. Синтетические наборы с управляемыми пропорциями — без обращения к продакшен-данным.

Когда TDC не нужен

  • Кроме одиночных значений вам больше ничего не понадобится. Их TDC тоже выдаёт — tdc.person.lastName(), без конфига и без файла, через API одного значения, — но у специализированного faker готовый каталог в коробке больше, а TDC везёт стартовый набор и докачивает остальное. TDC окупается там, где поля записи должны согласовываться между собой.

  • Нужна синтетическая копия продакшен-базы. TDC придумывает правдоподобные данные; он не изучает совместное распределение ваших продакшен-таблиц. Это другая задача.

  • Нужно обезличить продакшен-данные. TDC порождает новые записи; он не маскирует и не преобразует существующие.

  • Нужна фиксированная фикстура на пять строк. Для набора из нескольких статичных записей проще написать данные прямо в JSON.

  • Нужно создать нагрузку. TDC готовит тестовые данные; отправляют запросы инструменты вроде k6, JMeter и Locust.

Доступность

Опубликованы все пять. Одинаковые номера версий означают один и тот же движок: все пять держит общий набор фикстур, поэтому 0.1.6 из любого реестра даёт те же байты для того же конфига и сида.

РеализацияРеестрУстановкаВерсия
TypeScriptnpmnpm i tdcv20.1.7
PythonPyPIpip install tdcv20.1.7
Rustcrates.iocargo add tdcv20.1.7
C#NuGetdotnet add package Tdcv20.1.7
JavaMaven Centralio.github.nickliapin:tdcv20.1.7

Каждый опубликованный пакет несёт стартовый набор паков, поэтому работает без всего остального; остальные десять языков и девяносто с лишним пакетов стран — в одну команду.

С чего начать

примечание

Документация описывает то, что реализовано в текущей версии. Всё, что ещё в разработке, отмечено там, где встречается.