Генерація тестових даних за допомогою AI: від схеми до готового датасету
Уявіть: e-commerce база з 20 зв'язаними таблицями, ручне створення 10 000 записів займає тиждень, а після інтеграції тести падають через граничний випадок. Потрібні реалістичні дані: не просто user_1 та [email protected]. Щоб покрити всі сценарії, дані повинні мати правильні розподіли, граничні значення, різні символи та зв'язки між таблицями. Вручну це роблять погано: або даних мало, або вони нереалістичні.
Ручна генерація тестових даних забирає години й навіть дні. Потрібно придумати тисячі записів, узгодити зовнішні ключі, не забути про null та довгі рядки. AI-генерація тестових даних вирішує задачу за хвилини — масштабується від сотень до мільйонів записів без втрати якості. Ми автоматизуємо процес за допомогою LLM та кастомних промптів: створюємо структуровані датасети за схемою БД, семантичний контент під ваш домен та аномальні випадки для негативних тестів. Під ключ — від схеми до готового JSON/CSV.
Наш підхід дає покриття граничних випадків на рівні 10-15%. Ви отримуєте датасет, готовий для бойового тестування. Всі дані детерміновані та валідуються на льоту. Зв'яжіться з нами для консультації — ми підготуємо демо-датасет під вашу схему.
Проблеми, які вирішує AI-генерація
Ручні датасети містять 10-100 записів, а потрібно 100 000+. AI масштабується без втрати якості. Граничні випадки (порожні рядки, SQL-ін'єкції, рядки >1000 символів) вручну забувають у 80% проєктів. При кількох зв'язаних таблицях ручне створення порушує референційну цілісність — AI-генератор будує топологічний порядок і підтягує існуючі ID. Production-дані містять персональну інформацію — DataAnonymizer замінює email, телефон, паспорт на синтетичні аналоги, зберігаючи структуру.
Як AI забезпечує реалістичність даних?
Використовуємо комбінацію: Faker для базових провайдерів (імена, адреси); LLM (GPT-4o) для семантичного контексту — діагнози за МКХ-10, торгові найменування препаратів, суми з логнормальним розподілом; кастомні провайдери для доменів: медичні коди, IBAN. Температура моделі 0.8 дає різноманіття при збереженні правдоподібності.
Навіщо потрібні граничні випадки?
Граничні дані виявляють баги, які не відтворюються на «красивих» даних. Наш генератор включає 10-15% аномальних записів у кожному батчі. Це null, max int, спецсимволи, SQL-ін'єкції, довгі рядки. В одному проєкті e-commerce це допомогло знайти integer overflow при обробці великих сум — баг жив у продакшені 2 роки.
| Тип граничного випадку | Приклад | Вплив на тестування |
|---|---|---|
| Null значення | null, порожній рядок |
Перевірка обробки відсутніх даних |
| Максимальні значення | INT_MAX, рядок 1000+ символів |
Виявлення переповнення, обмежень довжини |
| Спеціальні символи | <script>, емодзі, SQL-ін'єкції |
Перевірка екранування, безпеки |
| Неправильні типи | рядок у числовому полі | Перевірка валідації типів |
| Дублікати | Повторювані унікальні ключі | Перевірка унікальності, індексів |
Як працює генерація зв'язаних даних?
Генератор будує топологічний порядок залежностей. Спочатку створюються батьківські таблиці, потім дочірні — з підстановкою існуючих FK. Це гарантує цілісність зв'язків. Замовте консультацію, і ми налаштуємо генератор під вашу схему.
Процес роботи
- Аналітика — вивчаємо схему БД, бізнес-правила, вимоги до тестових даних.
- Проектування — налаштовуємо провайдери Faker, пишемо промпти для LLM, визначаємо розподіли.
- Реалізація — код генератора, інтеграція з CI/CD (GitHub Actions, GitLab CI).
- Тестування — валідація датасету: схема, дублікати, FK, граничні випадки.
- Деплой — передача скрипту або API, навчання команди, документація.
Порівняння: ручна vs AI-генерація
| Параметр | Ручна генерація | AI-генерація (наша) |
|---|---|---|
| Швидкість | 100 записів за 1 годину | 100 000 записів за 10 хвилин |
| Покриття граничних випадків | < 1% | 10-15% за замовчуванням |
| Узгодженість FK | Часті помилки | Автоматична |
| Анонімізація | Напівручна | Повністю автоматична |
| Масштабованість | Обмежена | Лінійна до мільйонів записів |
AI-генератор створює датасети в 100 разів швидше за ручну працю.
Що входить у послугу
- Архітектура AI-генератора під вашу схему БД.
- Кастомні провайдери Faker для домену.
- Модуль анонімізації PII.
- Інтеграція з CI/CD (контейнеризація, Makefile).
- Документація з використання та налаштування.
- Навчання команди (1-2 години онлайн).
- Підтримка 2 тижні після здачі.
Практичний кейс
Наш клієнт — e-commerce платформа з тестовим оточенням з 50 000 рядків production-даних (анонімізованих). Після впровадження AI-генератора тестовий датасет розширено до 500 000 записів з реалістичними розподілами. Знайдено 3 продуктивнісних баги (slow queries), які не відтворювалися на малому датасеті. Граничні дані виявили помилку в обробці великих сум (integer overflow у старому PHP-коді). Економія часу тестування — 4 години на тиждень.
Код генерації
from faker import Faker from langchain_openai import ChatOpenAI from pydantic import BaseModel import json import random fake = Faker("uk_UA") class TestDataGenerator: SEMANTIC_PROMPT = """Згенеруй {count} реалістичних записів для таблиці. Схема таблиці: {schema} Контекст домену: {domain} Вимоги: - Дані повинні виглядати реалістично (не "test_value_1") - Числові значення в розумних діапазонах для домену - Дати розподілені по різних періодах - Статуси/категорії розподілені нерівномірно (реалістично) - Пов'язані поля узгоджені (напр., місто та регіон) - Включи 10-15% граничних випадків: min/max значення, порожні опціональні поля Поверни JSON-масив з {count} об'єктів.""" def __init__(self): self.llm = ChatOpenAI(model="gpt-4o", temperature=0.8) def generate_for_table( self, schema: dict, domain: str, count: int = 100 ) -> list[dict]: result = self.llm.invoke( self.SEMANTIC_PROMPT.format( count=min(count, 50), schema=json.dumps(schema, ensure_ascii=False, indent=2), domain=domain ) ) batch = json.loads(result.content) if count > 50: all_data = batch while len(all_data) < count: more = self.generate_for_table(schema, domain, min(50, count - len(all_data))) all_data.extend(more) return all_data[:count] return batch def generate_boundary_cases(self, schema: dict) -> list[dict]: boundary_prompt = f"""Створи граничні та невалідні тестові дані для схеми. Схема: {json.dumps(schema, ensure_ascii=False, indent=2)} Створи по 2–3 випадки кожного типу: 1. Порожні значення (null, "", []) 2. Максимальні значення (max int, max string length) 3. Мінімальні значення (min int, 0, negative) 4. Спеціальні символи: <, >, &, ', ", \\n, \\t, emoji 🎉 5. SQL-injection рядки (для перевірки sanitization) 6. Дуже довгі рядки (>1000 символів) 7. Неправильні типи (рядок замість числа тощо) Поверни JSON з позначкою expected_behavior для кожного випадку.""" return json.loads( self.llm.invoke(boundary_prompt).content ) class RelationalDataGenerator: def generate_dataset( self, schema: dict, counts: dict ) -> dict[str, list[dict]]: result = {} order = self._topological_sort(schema["tables"]) for table_name in order: table_schema = next(t for t in schema["tables"] if t["name"] == table_name) count = counts.get(table_name, 10) fk_pools = {} for fk in table_schema.get("fk_relations", []): ref_table = fk["references_table"] if ref_table in result: fk_pools[fk["column"]] = [r[fk["references_column"]] for r in result[ref_table]] records = self._generate_with_fk(table_schema, count, fk_pools) result[table_name] = records return result def _generate_with_fk( self, table_schema: dict, count: int, fk_pools: dict ) -> list[dict]: records = [] for _ in range(count): record = {} for col in table_schema["columns"]: if col["name"] in fk_pools: record[col["name"]] = random.choice(fk_pools[col["name"]]) else: record[col["name"]] = self._generate_field_value(col) records.append(record) return records from faker.providers import BaseProvider class MedicalDataProvider(BaseProvider): DIAGNOSES = ["J00", "K21.0", "I10", "E11", "M79.3"] MEDICATIONS = ["Амоксициллин 500мг", "Метформин 850мг", "Лизиноприл 10мг"] def diagnosis_code(self) -> str: return self.random_element(self.DIAGNOSES) def medication(self) -> str: return self.random_element(self.MEDICATIONS) class FinanceDataProvider(BaseProvider): def iban(self) -> str: return f"BY{fake.numerify('##')}ALFA{fake.numerify('################')}" def transaction_amount(self) -> float: weights = [0.5, 0.3, 0.15, 0.05] ranges = [(1, 100), (100, 1000), (1000, 10000), (10000, 100000)] chosen_range = random.choices(ranges, weights=weights)[0] return round(random.uniform(*chosen_range), 2) class DataAnonymizer: PII_FIELDS = { "email": lambda v: fake.email(), "phone": lambda v: fake.phone_number(), "name": lambda v: fake.name(), "inn": lambda v: fake.numerify("############"), "passport": lambda v: f"{fake.numerify('####')} {fake.numerify('######')}", "ip_address": lambda v: fake.ipv4_private(), "address": lambda v: fake.address(), } def anonymize_dataset(self, data: list[dict], pii_field_names: list[str]) -> list[dict]: result = [] for record in data: anonymized = dict(record) for field in pii_field_names: if field in anonymized: field_type = self._detect_field_type(field) if field_type in self.PII_FIELDS: anonymized[field] = self.PII_FIELDS[field_type](anonymized[field]) result.append(anonymized) return result Строки орієнтовно
- Генератор структурованих даних: 1–2 тижні.
- З анонімізацією та relational generation: 3–4 тижні.
Вартість розраховується індивідуально. Отримайте консультацію — ми підготуємо демо-датасет під вашу схему БД. Зв'яжіться з нами для оцінки вашого проєкту.







