تولید داده تست با هوش مصنوعی؛ ساخت Synthetic Data Generator و Mock API

در این آموزش یک AI Test Data Generator واقعی می‌سازید که نیاز متنی را به سناریوی ساختاریافته تبدیل می‌کند، داده فارسی معتبر می‌سازد، روابط را حفظ می‌کند و Mock API قابل اجرا ارائه می‌دهد.

Share
تولید داده تست با هوش مصنوعی؛ ساخت Synthetic Data Generator و Mock API

تولید داده تست با هوش مصنوعی؛ از Mock Data تا Mock API قابل اجرا

توسعه و تست نرم‌افزار بدون داده مناسب دشوار است. برای آزمایش یک فروشگاه اینترنتی فقط داشتن چند رکورد تصادفی کافی نیست. داده باید سناریوهای واقعی و مرزی را پوشش دهد:

  • مشتری فعال و غیرفعال
  • سفارش تکمیل‌شده، لغوشده و در انتظار
  • سبد خرید خالی
  • سفارش دارای تخفیف
  • محصول ناموجود
  • مقدار صفر
  • مبلغ نزدیک به حد مجاز
  • داده تکراری کنترل‌شده
  • فیلد اختیاری خالی
  • رابطه معتبر میان مشتری، سفارش و محصول
  • تاریخ‌های مختلف
  • متن فارسی
  • خطاهای مورد انتظار API

ابزارهای سنتی مانند Faker می‌توانند نام، آدرس، تاریخ و شماره تصادفی تولید کنند؛ اما نمی‌دانند برای آزمایش «گزارش مشتریان تکرارشونده» چه توزیع داده‌ای لازم است.

مدل هوش مصنوعی می‌تواند نیاز کسب‌وکار را درک و یک برنامه تولید داده بسازد:

برای آزمایش گزارش مشتریان تکرارشونده، حداقل ۳۰ درصد مشتریان باید
بیش از یک سفارش تکمیل‌شده داشته باشند و چند مشتری نیز فقط سفارش
لغوشده داشته باشند.

بااین‌حال، تولید هزاران ردیف مستقیماً توسط مدل زبانی انتخاب مناسبی نیست:

  • هزینه افزایش می‌یابد.
  • ساختار ممکن است ناپایدار باشد.
  • شناسه‌های تکراری ایجاد می‌شوند.
  • رابطه Foreign Key ممکن است بشکند.
  • توزیع عددی دقیق رعایت نمی‌شود.
  • بازتولید همان Dataset دشوار است.
  • محاسبات و Constraintها ممکن است نادرست باشند.

معماری بهتر:

  1. کاربر هدف تست را توضیح می‌دهد.
  2. مدل یک Data Generation Plan ساختاریافته تولید می‌کند.
  3. Backend برنامه را اعتبارسنجی می‌کند.
  4. Python و Faker داده‌ها را با Seed مشخص می‌سازند.
  5. Pydantic و قواعد دامنه خروجی را بررسی می‌کنند.
  6. تست کیفیت Dataset اجرا می‌شود.
  7. داده به JSON یا CSV صادر می‌شود.
  8. یک Mock API آن را در اختیار Frontend و تست‌ها قرار می‌دهد.

داده تست چیست؟

Test Data مجموعه‌ای از ورودی‌ها و وضعیت‌ها برای بررسی رفتار نرم‌افزار است.

انواع اصلی:

داده مسیر عادی

سناریوهای رایج و معتبر:

{
  "name": "کیبورد مکانیکی",
  "price": 3500000,
  "stock": 12,
  "is_active": true
}

داده مرزی

مقادیر نزدیک حداقل یا حداکثر:

{
  "quantity": 0
}
{
  "discount_rate": 1
}

داده نامعتبر

برای بررسی Validation:

{
  "price": -1000
}

داده رابطه‌ای

چند موجودیت مرتبط:

Customer → Order → Order Item → Product

داده حجمی

برای آزمایش Pagination، گزارش و عملکرد:

100,000 سفارش

داده Regression

سناریویی که یک Bug قبلی را بازتولید می‌کند.

تفاوت Fake Data و Synthetic Data

Fake Data

داده ساختگی ساده مانند نام، ایمیل و آدرس تصادفی.

Synthetic Data

داده مصنوعی با ساختار، توزیع و رابطه مشخص که برای شبیه‌سازی سناریو طراحی شده است.

مثال Fake Data:

{
  "customer": "کاربر نمونه",
  "order_amount": 1250000
}

مثال Synthetic Dataset:

- ۱۰۰۰ مشتری
- ۲۰ درصد مشتری غیرفعال
- ۳۵ درصد مشتری دارای خرید تکراری
- ۷۰ درصد سفارش تکمیل‌شده
- ۱۵ درصد لغوشده
- ۱۵ درصد در انتظار
- مبلغ سفارش بر اساس اقلام محاسبه شود
- هر Order Item به Product موجود متصل باشد

هوش مصنوعی در تولید داده تست چه نقشی دارد؟

تبدیل Requirement به Data Plan

ورودی:

برای تست داشبورد فروش، داده سه ماه با سفارش‌های موفق، لغوشده و
در انتظار می‌خواهم. هر شهر حداقل یک سفارش موفق داشته باشد.

خروجی:

{
  "entities": {
    "customers": 100,
    "products": 50,
    "orders": 1000
  },
  "distributions": {
    "order_status": {
      "completed": 0.70,
      "cancelled": 0.15,
      "pending": 0.15
    }
  },
  "constraints": [
    "هر شهر حداقل یک سفارش completed داشته باشد"
  ],
  "edge_cases": [
    "سفارش با مقدار صفر",
    "مشتری بدون سفارش",
    "محصول غیرفعال"
  ]
}

پیشنهاد Edge Case

مدل می‌تواند حالت‌های فراموش‌شده را پیشنهاد دهد.

تولید متن‌های واقع‌گرایانه

  • عنوان محصول
  • توضیح تیکت
  • متن بازخورد
  • نام کمپین
  • توضیح سفارش

تولید Test Fixture کوچک

برای تعداد محدود رکورد، مدل می‌تواند JSON نمونه تولید کند؛ خروجی همچنان باید با Schema بررسی شود.

ساخت Mock Response

بر اساس OpenAPI یا JSON Schema، نمونه Response موفق و خطا تولید می‌شود.

طراحی سناریو

مثلاً:

مشتری سفارش ایجاد می‌کند، پرداخت ناموفق می‌شود و در تلاش دوم
سفارش تکمیل می‌شود.

چرا داده را مستقیماً از مدل نگیریم؟

فرض کنید ۱۰ هزار سفارش می‌خواهیم. ارسال درخواست تولید تمام ردیف‌ها به مدل ممکن است باعث این مشکلات شود:

  • شماره سفارش تکراری
  • جمع مبلغ اشتباه
  • تاریخ خارج از بازه
  • وضعیت خارج از Enum
  • محصول موجودنبوده
  • مشتری نامعتبر
  • JSON ناقص
  • Dataset غیرقابل بازتولید

مدل برای طراحی Plan مناسب است. تولید ردیف‌ها بهتر است با کد قطعی انجام شود.

معماری AI Test Data Generator

Requirement
    ↓
Darvareh API
    ↓
Structured Data Plan
    ↓
Schema and Rule Validation
    ↓
Faker + Deterministic Generator
    ↓
Dataset Quality Tests
    ↓
JSON / CSV / Mock API

پروژه عملی این آموزش

یک فروشگاه نمونه با این موجودیت‌ها می‌سازیم:

  • Customer
  • Product
  • Order
  • Order Item

ابزار ما:

  • نیاز فارسی دریافت می‌کند.
  • تعداد و توزیع موجودیت‌ها را پیشنهاد می‌دهد.
  • Plan را با Pydantic بررسی می‌کند.
  • با Seed ثابت داده فارسی می‌سازد.
  • روابط را حفظ می‌کند.
  • مبلغ سفارش را از اقلام محاسبه می‌کند.
  • کیفیت Dataset را ارزیابی می‌کند.
  • داده را در فایل JSON ذخیره می‌کند.
  • APIهای Mock برای فهرست و جزئیات ارائه می‌دهد.

ایجاد پروژه

mkdir ai-test-data-generator
cd ai-test-data-generator

python -m venv .venv

فعال‌سازی در Linux و macOS:

source .venv/bin/activate

فعال‌سازی در Windows:

.venv\Scripts\Activate.ps1

نصب وابستگی‌ها:

pip install \
  openai \
  python-dotenv \
  pydantic \
  faker \
  fastapi \
  uvicorn

ساخت پوشه‌ها:

mkdir generator datasets output

فایل‌های زیر را ایجاد کنید:

generator/__init__.py
generator/schemas.py
generator/planner.py
generator/validator.py
generator/data_generator.py
generator/quality.py
generator/exporter.py
build_dataset.py
mock_api.py

تنظیم API درواره

فایل .env:

DARVAREH_API_KEY=YOUR_API_KEY
DARVAREH_MODEL=MODEL_ID_DARVAREH
DATASET_SEED=1405

فایل .gitignore:

.env
.venv/
__pycache__/
datasets/
output/

برای دریافت API Key در درواره ثبت‌نام کنید. Model ID مناسب را از صفحه مدل‌های درواره انتخاب کنید.

تعریف مدل داده فروشگاه

فایل generator/schemas.py:

from datetime import datetime
from typing import Literal

from pydantic import BaseModel, Field


OrderStatus = Literal[
    "completed",
    "pending",
    "cancelled",
]

CustomerStatus = Literal[
    "active",
    "inactive",
]

ProductCategory = Literal[
    "laptop",
    "monitor",
    "accessory",
    "mobile",
]


class Distribution(BaseModel):
    completed: float = Field(
        ge=0,
        le=1,
    )
    pending: float = Field(
        ge=0,
        le=1,
    )
    cancelled: float = Field(
        ge=0,
        le=1,
    )


class CustomerDistribution(BaseModel):
    active: float = Field(
        ge=0,
        le=1,
    )
    inactive: float = Field(
        ge=0,
        le=1,
    )


class EdgeCasePlan(BaseModel):
    name: str
    description: str
    count: int = Field(
        ge=1,
        le=100,
    )
    expected_use: str


class DataGenerationPlan(BaseModel):
    title: str
    purpose: str
    seed: int = Field(
        ge=0,
    )
    customer_count: int = Field(
        ge=1,
        le=100_000,
    )
    product_count: int = Field(
        ge=1,
        le=100_000,
    )
    order_count: int = Field(
        ge=1,
        le=1_000_000,
    )
    minimum_items_per_order: int = Field(
        ge=1,
        le=100,
    )
    maximum_items_per_order: int = Field(
        ge=1,
        le=100,
    )
    start_date: datetime
    end_date: datetime
    cities: list[str] = Field(
        min_length=1,
        max_length=100,
    )
    order_status_distribution: Distribution
    customer_status_distribution: (
        CustomerDistribution
    )
    repeat_customer_ratio: float = Field(
        ge=0,
        le=1,
    )
    inactive_product_ratio: float = Field(
        ge=0,
        le=1,
    )
    minimum_unit_price: int = Field(
        ge=0,
    )
    maximum_unit_price: int = Field(
        ge=0,
    )
    edge_cases: list[EdgeCasePlan] = Field(
        default_factory=list
    )
    assumptions: list[str] = Field(
        default_factory=list
    )


class Customer(BaseModel):
    id: str
    full_name: str
    city: str
    status: CustomerStatus
    created_at: datetime


class Product(BaseModel):
    id: str
    name: str
    category: ProductCategory
    unit_price: int = Field(
        ge=0,
    )
    stock: int = Field(
        ge=0,
    )
    is_active: bool


class OrderItem(BaseModel):
    product_id: str
    product_name: str
    quantity: int = Field(
        ge=1,
    )
    unit_price: int = Field(
        ge=0,
    )
    line_total: int = Field(
        ge=0,
    )


class Order(BaseModel):
    id: str
    customer_id: str
    status: OrderStatus
    created_at: datetime
    items: list[OrderItem] = Field(
        min_length=1
    )
    total_amount: int = Field(
        ge=0,
    )


class GeneratedDataset(BaseModel):
    metadata: dict
    customers: list[Customer]
    products: list[Product]
    orders: list[Order]

تولید Plan با API درواره

فایل generator/planner.py:

import json
import os

from dotenv import load_dotenv
from openai import OpenAI
from pydantic import ValidationError

from generator.schemas import (
    DataGenerationPlan,
)


load_dotenv()

api_key = os.getenv("DARVAREH_API_KEY")
model = os.getenv("DARVAREH_MODEL")
default_seed = int(
    os.getenv(
        "DATASET_SEED",
        "1405",
    )
)

if not api_key:
    raise RuntimeError(
        "DARVAREH_API_KEY is not configured."
    )

if not model:
    raise RuntimeError(
        "DARVAREH_MODEL is not configured."
    )

client = OpenAI(
    api_key=api_key,
    base_url="https://api.darvareh.ir/v1",
)


SYSTEM_PROMPT = """
تو یک مهندس تست و طراح داده مصنوعی هستی.

وظیفه:
Requirement فارسی را به یک Data Generation Plan تبدیل کن.

قواعد:
- خود Dataset را تولید نکن؛ فقط Plan بساز.
- مجموع توزیع وضعیت سفارش دقیقاً 1 باشد.
- مجموع توزیع وضعیت مشتری دقیقاً 1 باشد.
- تاریخ شروع پیش از تاریخ پایان باشد.
- حداقل تعداد اقلام از حداکثر بیشتر نباشد.
- حداقل قیمت از حداکثر بیشتر نباشد.
- حجم Dataset را متناسب با هدف پیشنهاد بده.
- Edge Caseها را صریح ثبت کن.
- رابطه Customer، Order و Product را در نظر بگیر.
- مبلغ سفارش در Backend محاسبه خواهد شد.
- فیلد یا موجودیت جدید اختراع نکن.
- خروجی فقط JSON معتبر باشد.
"""


OUTPUT_TEMPLATE = {
    "title": "فروشگاه آزمایشی",
    "purpose": "تست داشبورد فروش",
    "seed": 1405,
    "customer_count": 100,
    "product_count": 50,
    "order_count": 1000,
    "minimum_items_per_order": 1,
    "maximum_items_per_order": 5,
    "start_date": (
        "2026-05-01T00:00:00"
    ),
    "end_date": (
        "2026-07-31T23:59:59"
    ),
    "cities": [
        "تهران",
        "شیراز",
        "اصفهان",
    ],
    "order_status_distribution": {
        "completed": 0.70,
        "pending": 0.15,
        "cancelled": 0.15,
    },
    "customer_status_distribution": {
        "active": 0.80,
        "inactive": 0.20,
    },
    "repeat_customer_ratio": 0.35,
    "inactive_product_ratio": 0.10,
    "minimum_unit_price": 100000,
    "maximum_unit_price": 100000000,
    "edge_cases": [
        {
            "name": "customer_without_order",
            "description": (
                "مشتری بدون سفارش"
            ),
            "count": 5,
            "expected_use": (
                "تست گزارش مشتریان "
                "بدون خرید"
            ),
        }
    ],
    "assumptions": [
        "واحد مبلغ ریال است"
    ],
}


def generate_plan(
    requirement: str,
) -> DataGenerationPlan:
    response = client.chat.completions.create(
        model=model,
        temperature=0.1,
        messages=[
            {
                "role": "system",
                "content": SYSTEM_PROMPT,
            },
            {
                "role": "user",
                "content": (
                    "Requirement:\n"
                    + requirement
                    + "\n\nSeed پیش‌فرض:\n"
                    + str(default_seed)
                    + "\n\nقالب خروجی:\n"
                    + json.dumps(
                        OUTPUT_TEMPLATE,
                        ensure_ascii=False,
                        indent=2,
                    )
                ),
            },
        ],
    )

    raw_output = (
        response.choices[0]
        .message.content
    )

    if not raw_output:
        raise RuntimeError(
            "The model returned an "
            "empty plan."
        )

    try:
        parsed = json.loads(raw_output)
    except json.JSONDecodeError as error:
        raise RuntimeError(
            f"Invalid JSON from model: "
            f"{error}"
        ) from error

    try:
        return DataGenerationPlan.model_validate(
            parsed
        )
    except ValidationError as error:
        raise RuntimeError(
            f"Plan validation failed: "
            f"{error}"
        ) from error

اعتبارسنجی قواعد Plan

Pydantic نوع و محدوده هر فیلد را بررسی می‌کند، اما قواعد بین چند فیلد را باید جداگانه کنترل کنیم.

فایل generator/validator.py:

from decimal import Decimal

from generator.schemas import (
    DataGenerationPlan,
)


class PlanValidationError(
    ValueError
):
    pass


TOLERANCE = Decimal("0.000001")


def is_one(
    values: list[float],
) -> bool:
    total = sum(
        Decimal(str(value))
        for value in values
    )

    return abs(
        total - Decimal("1")
    ) <= TOLERANCE


def validate_plan(
    plan: DataGenerationPlan,
) -> None:
    if plan.start_date >= plan.end_date:
        raise PlanValidationError(
            "start_date must be before "
            "end_date."
        )

    if (
        plan.minimum_items_per_order
        > plan.maximum_items_per_order
    ):
        raise PlanValidationError(
            "Minimum items cannot exceed "
            "maximum items."
        )

    if (
        plan.minimum_unit_price
        > plan.maximum_unit_price
    ):
        raise PlanValidationError(
            "Minimum price cannot exceed "
            "maximum price."
        )

    order_distribution = (
        plan.order_status_distribution
    )

    if not is_one(
        [
            order_distribution.completed,
            order_distribution.pending,
            order_distribution.cancelled,
        ]
    ):
        raise PlanValidationError(
            "Order status distribution "
            "must sum to 1."
        )

    customer_distribution = (
        plan.customer_status_distribution
    )

    if not is_one(
        [
            customer_distribution.active,
            customer_distribution.inactive,
        ]
    ):
        raise PlanValidationError(
            "Customer status distribution "
            "must sum to 1."
        )

    if (
        plan.order_count > 0
        and plan.product_count == 0
    ):
        raise PlanValidationError(
            "Orders require products."
        )

تولید تصادفی قابل بازتولید

Seed باعث می‌شود اجرای یک Plan مشخص، Dataset یکسانی تولید کند:

random.Random(plan.seed)

Faker نیز باید Seed دریافت کند:

Faker.seed(plan.seed)

بدون Seed، عیب‌یابی Test شکست‌خورده دشوار می‌شود.

تولید نام محصول فارسی

فایل generator/catalog.py:

PRODUCT_NAMES = {
    "laptop": [
        "لپ‌تاپ مدل آریا",
        "لپ‌تاپ مدل دنا",
        "لپ‌تاپ مدل پارس",
        "لپ‌تاپ مدل سپهر",
    ],
    "monitor": [
        "مانیتور ۲۴ اینچ",
        "مانیتور ۲۷ اینچ",
        "مانیتور حرفه‌ای",
        "مانیتور اداری",
    ],
    "accessory": [
        "ماوس بی‌سیم",
        "کیبورد مکانیکی",
        "هاب USB-C",
        "پایه لپ‌تاپ",
        "هدفون اداری",
    ],
    "mobile": [
        "گوشی مدل آلفا",
        "گوشی مدل نوا",
        "گوشی مدل پلاس",
        "گوشی مدل پرو",
    ],
}

برای فیلدهای محدود و ساختاری بهتر است از فهرست کنترل‌شده استفاده شود، نه تولید آزاد مدل.

تولید Dataset

فایل generator/data_generator.py:

import random
from datetime import (
    datetime,
    timedelta,
)

from faker import Faker

from generator.catalog import (
    PRODUCT_NAMES,
)
from generator.schemas import (
    Customer,
    DataGenerationPlan,
    GeneratedDataset,
    Order,
    OrderItem,
    Product,
)


CATEGORIES = [
    "laptop",
    "monitor",
    "accessory",
    "mobile",
]


def weighted_choice(
    randomizer: random.Random,
    values: list[str],
    weights: list[float],
) -> str:
    return randomizer.choices(
        values,
        weights=weights,
        k=1,
    )[0]


def random_datetime(
    randomizer: random.Random,
    start: datetime,
    end: datetime,
) -> datetime:
    total_seconds = int(
        (end - start).total_seconds()
    )

    offset = randomizer.randint(
        0,
        total_seconds,
    )

    return start + timedelta(
        seconds=offset
    )


def generate_customers(
    plan: DataGenerationPlan,
    randomizer: random.Random,
    fake: Faker,
) -> list[Customer]:
    customers = []

    statuses = [
        "active",
        "inactive",
    ]

    weights = [
        (
            plan.customer_status_distribution
            .active
        ),
        (
            plan.customer_status_distribution
            .inactive
        ),
    ]

    for index in range(
        1,
        plan.customer_count + 1,
    ):
        customer = Customer(
            id=f"C-{index:06d}",
            full_name=fake.name(),
            city=randomizer.choice(
                plan.cities
            ),
            status=weighted_choice(
                randomizer,
                statuses,
                weights,
            ),
            created_at=random_datetime(
                randomizer,
                plan.start_date
                - timedelta(days=365),
                plan.end_date,
            ),
        )

        customers.append(customer)

    return customers


def generate_products(
    plan: DataGenerationPlan,
    randomizer: random.Random,
) -> list[Product]:
    products = []

    inactive_count = round(
        plan.product_count
        * plan.inactive_product_ratio
    )

    inactive_indexes = set(
        randomizer.sample(
            range(plan.product_count),
            k=min(
                inactive_count,
                plan.product_count,
            ),
        )
    )

    for index in range(
        plan.product_count
    ):
        category = randomizer.choice(
            CATEGORIES
        )

        base_name = randomizer.choice(
            PRODUCT_NAMES[category]
        )

        product = Product(
            id=f"P-{index + 1:06d}",
            name=(
                f"{base_name} "
                f"{index + 1}"
            ),
            category=category,
            unit_price=randomizer.randint(
                plan.minimum_unit_price,
                plan.maximum_unit_price,
            ),
            stock=randomizer.randint(
                0,
                500,
            ),
            is_active=(
                index
                not in inactive_indexes
            ),
        )

        products.append(product)

    return products


def select_order_customers(
    plan: DataGenerationPlan,
    customers: list[Customer],
    randomizer: random.Random,
) -> list[Customer]:
    repeat_pool_size = max(
        1,
        round(
            len(customers)
            * plan.repeat_customer_ratio
        ),
    )

    repeat_pool = randomizer.sample(
        customers,
        k=min(
            repeat_pool_size,
            len(customers),
        ),
    )

    selected = []

    for _ in range(plan.order_count):
        use_repeat_customer = (
            randomizer.random()
            < plan.repeat_customer_ratio
        )

        source = (
            repeat_pool
            if use_repeat_customer
            else customers
        )

        selected.append(
            randomizer.choice(source)
        )

    return selected


def generate_orders(
    plan: DataGenerationPlan,
    customers: list[Customer],
    products: list[Product],
    randomizer: random.Random,
) -> list[Order]:
    orders = []

    statuses = [
        "completed",
        "pending",
        "cancelled",
    ]

    status_weights = [
        (
            plan.order_status_distribution
            .completed
        ),
        (
            plan.order_status_distribution
            .pending
        ),
        (
            plan.order_status_distribution
            .cancelled
        ),
    ]

    selected_customers = (
        select_order_customers(
            plan,
            customers,
            randomizer,
        )
    )

    for index in range(
        1,
        plan.order_count + 1,
    ):
        item_count = randomizer.randint(
            plan.minimum_items_per_order,
            min(
                plan.maximum_items_per_order,
                len(products),
            ),
        )

        selected_products = (
            randomizer.sample(
                products,
                k=item_count,
            )
        )

        items = []

        for product in selected_products:
            quantity = randomizer.randint(
                1,
                5,
            )

            line_total = (
                product.unit_price
                * quantity
            )

            items.append(
                OrderItem(
                    product_id=product.id,
                    product_name=(
                        product.name
                    ),
                    quantity=quantity,
                    unit_price=(
                        product.unit_price
                    ),
                    line_total=line_total,
                )
            )

        total_amount = sum(
            item.line_total
            for item in items
        )

        orders.append(
            Order(
                id=f"O-{index:08d}",
                customer_id=(
                    selected_customers[
                        index - 1
                    ].id
                ),
                status=weighted_choice(
                    randomizer,
                    statuses,
                    status_weights,
                ),
                created_at=random_datetime(
                    randomizer,
                    plan.start_date,
                    plan.end_date,
                ),
                items=items,
                total_amount=(
                    total_amount
                ),
            )
        )

    return orders


def generate_dataset(
    plan: DataGenerationPlan,
) -> GeneratedDataset:
    randomizer = random.Random(
        plan.seed
    )

    Faker.seed(plan.seed)
    fake = Faker("fa_IR")
    fake.seed_instance(plan.seed)

    customers = generate_customers(
        plan,
        randomizer,
        fake,
    )

    products = generate_products(
        plan,
        randomizer,
    )

    orders = generate_orders(
        plan,
        customers,
        products,
        randomizer,
    )

    return GeneratedDataset(
        metadata={
            "title": plan.title,
            "purpose": plan.purpose,
            "seed": plan.seed,
            "generated_at": (
                datetime.utcnow()
                .isoformat()
                + "Z"
            ),
            "currency": "IRR",
        },
        customers=customers,
        products=products,
        orders=orders,
    )

در Python جدیدتر بهتر است از datetime.now(timezone.utc) استفاده شود. در نمونه عملیاتی زمان تولید را با Timezone صریح ثبت کنید.

بررسی کیفیت Dataset

فایل generator/quality.py:

from collections import Counter

from generator.schemas import (
    DataGenerationPlan,
    GeneratedDataset,
)


class DatasetQualityError(
    ValueError
):
    pass


def validate_dataset(
    plan: DataGenerationPlan,
    dataset: GeneratedDataset,
) -> dict:
    errors = []
    warnings = []

    if (
        len(dataset.customers)
        != plan.customer_count
    ):
        errors.append(
            "Customer count does not "
            "match the plan."
        )

    if (
        len(dataset.products)
        != plan.product_count
    ):
        errors.append(
            "Product count does not "
            "match the plan."
        )

    if (
        len(dataset.orders)
        != plan.order_count
    ):
        errors.append(
            "Order count does not "
            "match the plan."
        )

    customer_ids = {
        customer.id
        for customer in (
            dataset.customers
        )
    }

    product_ids = {
        product.id
        for product in (
            dataset.products
        )
    }

    if (
        len(customer_ids)
        != len(dataset.customers)
    ):
        errors.append(
            "Duplicate customer IDs found."
        )

    if (
        len(product_ids)
        != len(dataset.products)
    ):
        errors.append(
            "Duplicate product IDs found."
        )

    order_ids = {
        order.id
        for order in dataset.orders
    }

    if (
        len(order_ids)
        != len(dataset.orders)
    ):
        errors.append(
            "Duplicate order IDs found."
        )

    for order in dataset.orders:
        if (
            order.customer_id
            not in customer_ids
        ):
            errors.append(
                f"Order {order.id} references "
                "an unknown customer."
            )

        expected_total = 0

        for item in order.items:
            if (
                item.product_id
                not in product_ids
            ):
                errors.append(
                    f"Order {order.id} references "
                    "an unknown product."
                )

            expected_line_total = (
                item.quantity
                * item.unit_price
            )

            if (
                item.line_total
                != expected_line_total
            ):
                errors.append(
                    f"Invalid line total in "
                    f"order {order.id}."
                )

            expected_total += (
                item.line_total
            )

        if (
            order.total_amount
            != expected_total
        ):
            errors.append(
                f"Invalid total amount in "
                f"order {order.id}."
            )

    status_counts = Counter(
        order.status
        for order in dataset.orders
    )

    cities_with_completed_order = {
        customer.city
        for customer in (
            dataset.customers
        )
        if any(
            order.customer_id
            == customer.id
            and order.status
            == "completed"
            for order in dataset.orders
        )
    }

    missing_cities = (
        set(plan.cities)
        - cities_with_completed_order
    )

    if missing_cities:
        warnings.append(
            "Some cities have no completed "
            "orders: "
            + ", ".join(
                sorted(missing_cities)
            )
        )

    if errors:
        raise DatasetQualityError(
            "\n".join(errors[:20])
        )

    return {
        "valid": True,
        "customer_count": len(
            dataset.customers
        ),
        "product_count": len(
            dataset.products
        ),
        "order_count": len(
            dataset.orders
        ),
        "order_status_counts": dict(
            status_counts
        ),
        "warnings": warnings,
    }

این مرحله از پذیرش Dataset دارای Foreign Key یا مبلغ اشتباه جلوگیری می‌کند.

نکته مهم درباره توزیع احتمالی

اگر Plan می‌گوید:

completed = 70%

در نمونه تصادفی کوچک لزوماً دقیقاً ۷۰ درصد رکوردها completed نمی‌شوند. دو رویکرد وجود دارد:

توزیع احتمالی

برای هر سفارش با وزن مشخص انتخاب می‌شود. نسبت نهایی تقریباً نزدیک است.

توزیع دقیق

تعداد هر وضعیت از قبل محاسبه و سپس فهرست Shuffle می‌شود.

برای تست گزارش‌های دقیق، روش دوم بهتر است.

def build_exact_statuses(
    order_count,
    distribution,
    randomizer,
):
    completed_count = round(
        order_count
        * distribution.completed
    )

    pending_count = round(
        order_count
        * distribution.pending
    )

    cancelled_count = (
        order_count
        - completed_count
        - pending_count
    )

    statuses = (
        ["completed"] * completed_count
        + ["pending"] * pending_count
        + ["cancelled"]
        * cancelled_count
    )

    randomizer.shuffle(statuses)

    return statuses

ساخت فایل Plan و Dataset

فایل build_dataset.py:

from pathlib import Path

from generator.data_generator import (
    generate_dataset,
)
from generator.planner import (
    generate_plan,
)
from generator.quality import (
    validate_dataset,
)
from generator.validator import (
    validate_plan,
)


REQUIREMENT = """
برای تست داشبورد فروش یک فروشگاه اینترنتی داده بساز.

نیازها:
- 200 مشتری
- 80 محصول
- 3000 سفارش
- بازه سه‌ماهه
- شهرهای تهران، شیراز، اصفهان، مشهد و تبریز
- بیشتر سفارش‌ها تکمیل‌شده باشند
- بخشی از مشتریان خرید تکراری داشته باشند
- چند مشتری بدون سفارش وجود داشته باشند
- چند محصول غیرفعال باشد
- واحد مبلغ ریال باشد
- داده برای تست فیلتر، نمودار و Pagination مناسب باشد
"""


def main():
    plan = generate_plan(
        REQUIREMENT
    )

    validate_plan(plan)

    plan_path = Path(
        "output/data-plan.json"
    )

    plan_path.parent.mkdir(
        parents=True,
        exist_ok=True,
    )

    plan_path.write_text(
        plan.model_dump_json(indent=2),
        encoding="utf-8",
    )

    dataset = generate_dataset(
        plan
    )

    quality_report = validate_dataset(
        plan,
        dataset,
    )

    dataset_path = Path(
        "datasets/store-dataset.json"
    )

    dataset_path.parent.mkdir(
        parents=True,
        exist_ok=True,
    )

    dataset_path.write_text(
        dataset.model_dump_json(indent=2),
        encoding="utf-8",
    )

    quality_path = Path(
        "output/quality-report.json"
    )

    import json

    quality_path.write_text(
        json.dumps(
            quality_report,
            ensure_ascii=False,
            indent=2,
        ),
        encoding="utf-8",
    )

    print(
        f"Plan saved to {plan_path}"
    )
    print(
        f"Dataset saved to {dataset_path}"
    )
    print(
        f"Quality report saved to "
        f"{quality_path}"
    )


if __name__ == "__main__":
    main()

اجرا:

python build_dataset.py

خروجی‌های پروژه

output/data-plan.json
output/quality-report.json
datasets/store-dataset.json

با Seed ثابت، اجرای دوباره همان Plan باید داده یکسانی تولید کند؛ به‌جز Metadata زمانی که باید برای مقایسه نادیده گرفته یا ثابت شود.

خروجی CSV

برای تحلیل داده ممکن است CSV مناسب‌تر باشد.

فایل generator/exporter.py:

import csv
from pathlib import Path

from generator.schemas import (
    GeneratedDataset,
)


def export_orders_csv(
    dataset: GeneratedDataset,
    output_path: Path,
) -> None:
    output_path.parent.mkdir(
        parents=True,
        exist_ok=True,
    )

    customer_map = {
        customer.id: customer
        for customer in (
            dataset.customers
        )
    }

    with output_path.open(
        "w",
        encoding="utf-8-sig",
        newline="",
    ) as output_file:
        writer = csv.DictWriter(
            output_file,
            fieldnames=[
                "order_id",
                "created_at",
                "customer_id",
                "customer_city",
                "status",
                "item_count",
                "total_amount",
                "currency",
            ],
        )

        writer.writeheader()

        for order in dataset.orders:
            customer = customer_map[
                order.customer_id
            ]

            writer.writerow(
                {
                    "order_id": order.id,
                    "created_at": (
                        order.created_at
                        .isoformat()
                    ),
                    "customer_id": (
                        order.customer_id
                    ),
                    "customer_city": (
                        customer.city
                    ),
                    "status": order.status,
                    "item_count": len(
                        order.items
                    ),
                    "total_amount": (
                        order.total_amount
                    ),
                    "currency": "IRR",
                }
            )

استفاده از utf-8-sig نمایش فارسی را در بسیاری از نسخه‌های Excel بهتر می‌کند.

ساخت Mock API

فایل mock_api.py:

from pathlib import Path

from fastapi import (
    FastAPI,
    HTTPException,
    Query,
)

from generator.schemas import (
    GeneratedDataset,
)


DATASET_PATH = Path(
    "datasets/store-dataset.json"
)

if not DATASET_PATH.exists():
    raise RuntimeError(
        "Dataset does not exist. "
        "Run build_dataset.py first."
    )

dataset = (
    GeneratedDataset.model_validate_json(
        DATASET_PATH.read_text(
            encoding="utf-8"
        )
    )
)

app = FastAPI(
    title="Store Mock API",
    version="1.0.0",
)


@app.get("/health")
def health_check():
    return {
        "status": "ok",
    }


@app.get("/customers")
def list_customers(
    page: int = Query(
        default=1,
        ge=1,
    ),
    page_size: int = Query(
        default=20,
        ge=1,
        le=100,
    ),
    city: str | None = None,
    status: str | None = None,
):
    customers = dataset.customers

    if city:
        customers = [
            customer
            for customer in customers
            if customer.city == city
        ]

    if status:
        customers = [
            customer
            for customer in customers
            if customer.status == status
        ]

    offset = (
        page - 1
    ) * page_size

    page_items = customers[
        offset:offset + page_size
    ]

    return {
        "items": [
            item.model_dump(
                mode="json"
            )
            for item in page_items
        ],
        "page": page,
        "page_size": page_size,
        "total_items": len(customers),
    }


@app.get("/customers/{customer_id}")
def get_customer(
    customer_id: str,
):
    customer = next(
        (
            item
            for item in (
                dataset.customers
            )
            if item.id == customer_id
        ),
        None,
    )

    if customer is None:
        raise HTTPException(
            status_code=404,
            detail="Customer not found",
        )

    return customer.model_dump(
        mode="json"
    )


@app.get("/products")
def list_products(
    active_only: bool = False,
):
    products = dataset.products

    if active_only:
        products = [
            product
            for product in products
            if product.is_active
        ]

    return [
        product.model_dump(
            mode="json"
        )
        for product in products
    ]


@app.get("/orders")
def list_orders(
    page: int = Query(
        default=1,
        ge=1,
    ),
    page_size: int = Query(
        default=20,
        ge=1,
        le=100,
    ),
    status: str | None = None,
    customer_id: str | None = None,
):
    orders = dataset.orders

    if status:
        orders = [
            order
            for order in orders
            if order.status == status
        ]

    if customer_id:
        orders = [
            order
            for order in orders
            if (
                order.customer_id
                == customer_id
            )
        ]

    offset = (
        page - 1
    ) * page_size

    page_items = orders[
        offset:offset + page_size
    ]

    return {
        "items": [
            order.model_dump(
                mode="json"
            )
            for order in page_items
        ],
        "page": page,
        "page_size": page_size,
        "total_items": len(orders),
    }


@app.get("/orders/{order_id}")
def get_order(
    order_id: str,
):
    order = next(
        (
            item
            for item in dataset.orders
            if item.id == order_id
        ),
        None,
    )

    if order is None:
        raise HTTPException(
            status_code=404,
            detail="Order not found",
        )

    return order.model_dump(
        mode="json"
    )

اجرای Mock API

uvicorn mock_api:app --reload

Swagger UI:

http://127.0.0.1:8000/docs

آزمایش Mock API

فهرست سفارش‌ها:

curl \
  "http://127.0.0.1:8000/orders?page=1&page_size=10"

فقط سفارش‌های تکمیل‌شده:

curl \
  "http://127.0.0.1:8000/orders?status=completed"

جزئیات سفارش:

curl \
  "http://127.0.0.1:8000/orders/O-00000001"

مشتریان تهران:

curl \
  "http://127.0.0.1:8000/customers?city=تهران"

Mock API مبتنی بر سناریو

فقط داشتن داده کافی نیست. Frontend باید حالت‌های مختلف API را نیز آزمایش کند:

  • پاسخ موفق
  • فهرست خالی
  • 404
  • تأخیر
  • خطای موقت
  • Pagination آخر
  • Response ناقص کنترل‌شده

می‌توان Query Parameter آزمایشی اضافه کرد:

from time import sleep


@app.get("/scenario/orders")
def scenario_orders(
    scenario: str = "success",
):
    if scenario == "empty":
        return {
            "items": [],
            "total_items": 0,
        }

    if scenario == "not_found":
        raise HTTPException(
            status_code=404,
            detail="Order not found",
        )

    if scenario == "slow":
        sleep(2)

    return {
        "items": [
            dataset.orders[0].model_dump(
                mode="json"
            )
        ],
        "total_items": 1,
    }

این Endpoint فقط برای محیط Mock مناسب است و نباید با API واقعی مخلوط شود.

تولید داده از JSON Schema

اگر Schema دارید، داده باید با همان Contract تولید شود:

{
  "type": "object",
  "required": [
    "id",
    "name",
    "price"
  ],
  "properties": {
    "id": {
      "type": "string",
      "pattern": "^P-[0-9]{6}$"
    },
    "name": {
      "type": "string",
      "minLength": 2
    },
    "price": {
      "type": "integer",
      "minimum": 0
    }
  }
}

معماری مناسب:

  1. JSON Schema Parse شود.
  2. Constraintهای قطعی با کد اجرا شوند.
  3. مدل فقط Context معنایی و سناریو پیشنهاد دهد.
  4. خروجی با همان Schema اعتبارسنجی شود.

تولید Mock Data از OpenAPI

OpenAPI می‌تواند منبع این اطلاعات باشد:

  • Request Body
  • Response Schema
  • پارامترها
  • Enum
  • Required
  • Example
  • Status Code

مدل می‌تواند برای هر Endpoint سناریو پیشنهاد کند:

{
  "operation_id": "get_order",
  "scenarios": [
    {
      "name": "success",
      "status_code": 200
    },
    {
      "name": "not_found",
      "status_code": 404
    }
  ]
}

Status Code موجودنبوده نباید اختراع شود.

تولید Test Fixture کوچک با مدل

برای Fixture متنی محدود می‌توان مستقیم از مدل استفاده کرد:

برای Schema زیر ۵ نمونه تیکت پشتیبانی فارسی تولید کن.

توزیع:
- ۲ تیکت پشتیبانی فنی
- ۱ سؤال صورتحساب
- ۱ درخواست قابلیت
- ۱ بازخورد مثبت

قواعد:
- خروجی JSON معتبر باشد.
- ID یکتا باشد.
- category فقط از Enum استفاده کند.
- متن هر تیکت حداکثر ۱۲۰ کلمه باشد.

سپس خروجی با Pydantic بررسی شود. برای حجم زیاد، مدل فقط Template و Plan بدهد.

داده مرزی را تصادفی نسازید

Edge Caseهای مهم بهتر است به‌صورت قطعی ایجاد شوند:

EDGE_CASE_PRODUCTS = [
    Product(
        id="P-EDGE-001",
        name="محصول بدون موجودی",
        category="accessory",
        unit_price=100000,
        stock=0,
        is_active=True,
    ),
    Product(
        id="P-EDGE-002",
        name="محصول غیرفعال",
        category="monitor",
        unit_price=5000000,
        stock=10,
        is_active=False,
    ),
]

اگر Edge Case فقط به شانس سپرده شود، ممکن است در اجرای بعدی وجود نداشته باشد.

تضمین حداقل یک سناریو

اگر هر شهر باید سفارش موفق داشته باشد، ابتدا این رکوردهای تضمینی ساخته شوند و سپس داده تصادفی اضافه شود.

def ensure_completed_order_per_city(
    plan,
    customers,
    products,
    orders,
):
    for city in plan.cities:
        city_customer = next(
            customer
            for customer in customers
            if customer.city == city
        )

        product = products[0]

        item = OrderItem(
            product_id=product.id,
            product_name=product.name,
            quantity=1,
            unit_price=product.unit_price,
            line_total=product.unit_price,
        )

        orders.append(
            Order(
                id=(
                    f"O-GUARANTEED-"
                    f"{len(orders) + 1}"
                ),
                customer_id=city_customer.id,
                status="completed",
                created_at=plan.start_date,
                items=[item],
                total_amount=(
                    item.line_total
                ),
            )
        )

تعداد این رکوردها باید در order_count کل محاسبه شود تا Dataset بزرگ‌تر از Plan نشود.

حفظ Referential Integrity

قواعد مهم:

  • order.customer_id باید در Customerها باشد.
  • item.product_id باید در Productها باشد.
  • IDها باید یکتا باشند.
  • مجموع سفارش از اقلام محاسبه شود.
  • تعداد و قیمت با Line Total سازگار باشند.
  • تاریخ سفارش در بازه Plan باشد.
  • Enumها معتبر باشند.

این قواعد باید با کد بررسی شوند، نه با اعتماد به مدل.

داده برای Pagination

برای تست Pagination:

  • تعداد رکورد از page_size بیشتر باشد.
  • تعداد دقیقاً مضرب page_size آزمایش شود.
  • تعداد یک واحد کمتر از مضرب آزمایش شود.
  • تعداد یک واحد بیشتر از مضرب آزمایش شود.
  • صفحه اول
  • صفحه آخر
  • صفحه بعد از آخر
  • page_size=1
  • حداکثر page_size

Fixtureهای مناسب:

0, 1, 9, 10, 11, 19, 20, 21 رکورد

داده برای تست جست‌وجو

Dataset باید شامل موارد نزدیک باشد:

کیبورد مکانیکی
کیبورد بی‌سیم
کیبورد
کاور کیبورد

همچنین:

  • فاصله اضافی
  • نیم‌فاصله
  • حروف فارسی و عربی مشابه
  • متن کوتاه
  • متن طولانی
  • نام تکراری
  • حروف انگلیسی

داده برای تست مرتب‌سازی

برای Sort باید این موارد وجود داشته باشند:

  • مقدار مساوی
  • مقدار صفر
  • کمترین و بیشترین مقدار
  • تاریخ مساوی
  • مقدار خالی در صورت مجازبودن
  • چند رکورد با کلید Sort یکسان

در صورت برابر بودن مقدار اصلی، Tie-breaker نیز باید مشخص باشد.

Snapshot یا Seed ثابت؟

Seed ثابت

Dataset با الگوریتم یکسان دوباره تولید می‌شود.

Snapshot

خروجی کامل در Git ذخیره و با نسخه مرجع مقایسه می‌شود.

روش پیشنهادی:

  • Seed و Plan در Git ذخیره شوند.
  • Dataset بسیار بزرگ در Git ذخیره نشود.
  • Fixtureهای کوچک و مهم Snapshot شوند.
  • نسخه Generator ثبت شود.

Metadata:

{
  "generator_version": "1.0.0",
  "seed": 1405,
  "plan_hash": "HASH",
  "record_counts": {
    "customers": 200,
    "products": 80,
    "orders": 3000
  }
}

Hash کردن Plan

import hashlib
import json


def calculate_plan_hash(
    plan,
) -> str:
    normalized = json.dumps(
        plan.model_dump(
            mode="json"
        ),
        ensure_ascii=False,
        sort_keys=True,
        separators=(",", ":"),
    )

    return hashlib.sha256(
        normalized.encode("utf-8")
    ).hexdigest()

اگر Plan، Seed یا نسخه Generator تغییر کند، خروجی نیز ممکن است تغییر کند.

تست Generator

فایل tests/test_generator.py:

from generator.data_generator import (
    generate_dataset,
)
from generator.quality import (
    validate_dataset,
)


def test_generation_is_reproducible(
    sample_plan,
):
    first = generate_dataset(
        sample_plan
    )

    second = generate_dataset(
        sample_plan
    )

    first_data = first.model_dump()
    second_data = second.model_dump()

    first_data["metadata"].pop(
        "generated_at",
        None,
    )

    second_data["metadata"].pop(
        "generated_at",
        None,
    )

    assert first_data == second_data


def test_all_order_relations_are_valid(
    sample_plan,
):
    dataset = generate_dataset(
        sample_plan
    )

    report = validate_dataset(
        sample_plan,
        dataset,
    )

    assert report["valid"] is True

استفاده در Frontend

Frontend می‌تواند Base URL را در محیط توسعه تغییر دهد:

VITE_API_BASE_URL=http://localhost:8000

یا در Next.js:

NEXT_PUBLIC_API_BASE_URL=http://localhost:8000

این متغیر فقط URL عمومی Mock API است و نباید API Key درواره در متغیر عمومی قرار گیرد.

استفاده در Postman

پس از اجرای Mock API:

http://127.0.0.1:8000/openapi.json

این فایل را می‌توان وارد Postman کرد و Collection ساخت. راهنمای عملی Postman در مقاله اتصال API درواره به Postman توضیح داده شده است.

ارزیابی AI Test Data Generator

Dataset ارزیابی Plan باید شامل این نیازها باشد:

  • فروشگاه
  • CRM
  • تیکت پشتیبانی
  • Pagination
  • گزارش زمانی
  • داده چندموجودیتی
  • Enum
  • داده خالی
  • Edge Case
  • Requirement مبهم
  • Constraint متناقض
  • Dataset کوچک
  • Dataset حجیم

معیارها:

معیارتوضیح
Schema Validityتمام رکوردها با Schema سازگارند
Referential Integrityرابطه‌ها معتبرند
Distribution Accuracyتوزیع با Plan هماهنگ است
Edge Case Coverageسناریوهای مرزی موجودند
ReproducibilitySeed یکسان خروجی یکسان می‌دهد
Arithmetic Accuracyجمع‌ها درست‌اند
Uniquenessشناسه تکراری وجود ندارد
Requirement AlignmentDataset هدف تست را پوشش می‌دهد
Generation Timeزمان تولید
Human Edit Rateمیزان اصلاح Plan

انتخاب مدل مناسب

مدل برای این وظایف باید در موارد زیر مناسب باشد:

  • درک Requirement فارسی
  • طراحی Test Scenario
  • پیشنهاد Edge Case
  • تولید JSON معتبر
  • پیروی از Schema
  • تشخیص Constraint متناقض
  • تولید متن فارسی طبیعی
  • ساخت Plan، نه تولید حجیم ردیف‌ها

برای Plan ساده، مدل سریع و اقتصادی کافی است. برای دامنه‌های پیچیده و موجودیت‌های مرتبط، مدل قوی‌تر ممکن است Plan دقیق‌تری ارائه دهد.

مدل‌ها و قیمت‌های به‌روز را در صفحه مدل‌های درواره ببینید.

مدیریت هزینه

مدل فقط Plan بسازد

هزاران ردیف با Faker و Python تولید شوند.

Cache کردن Plan

کلید Cache:

requirement_hash +
schema_version +
model_id +
prompt_version

استفاده مجدد از Fixtureها

سناریوهای تأییدشده دوباره تولید نشوند.

تولید متن محدود

فقط فیلدهای متنی نیازمند تنوع با مدل ساخته شوند.

اجرای Batch

اگر چند Template متنی لازم است، در یک درخواست ساختاریافته تولید شوند.

اشتباهات رایج

تولید تمام Dataset با مدل

برای حجم بالا از Generator قطعی استفاده کنید.

نداشتن Seed

Bug شکست‌خورده قابل بازتولید نخواهد بود.

داده کاملاً تصادفی

Edge Caseهای مهم باید تضمین‌شده باشند.

بررسی‌نکردن Foreign Key

رابطه‌ها باید پس از تولید اعتبارسنجی شوند.

اعتماد به مبلغ تولیدشده

Line Total و Order Total در Backend محاسبه شوند.

تغییر Test برای هماهنگی با داده

Specification تعیین می‌کند Dataset چه سناریویی باید بسازد.

استفاده از یک Dataset برای همه تست‌ها

Unit Test، Integration Test، UI Test و Performance Test نیازهای متفاوتی دارند.

قرار دادن Mock API در مسیر Production

Mock Server باید محیط و استقرار جدا داشته باشد.

نقشه راه Production

مرحله اول: Plan

مدل فقط Plan و Edge Case پیشنهاد دهد.

مرحله دوم: Generator

داده با Seed و Faker تولید شود.

مرحله سوم: Quality Gate

Schema، رابطه، توزیع و جمع‌ها بررسی شوند.

مرحله چهارم: Export

JSON، CSV و Fixtureهای کوچک ساخته شوند.

مرحله پنجم: Mock API

Frontend و Integration Test از API استفاده کنند.

مرحله ششم: CI

Generator و Quality Test در Pipeline اجرا شوند.

مرحله هفتم: Scenario Library

Planهای تأییدشده نسخه‌بندی و استفاده مجدد شوند.

چک‌لیست تولید داده تست

  • هدف Dataset مشخص است.
  • Schema تعریف شده است.
  • Plan ساختاریافته است.
  • Constraintها اعتبارسنجی می‌شوند.
  • Seed ثابت وجود دارد.
  • شناسه‌ها یکتا هستند.
  • Foreign Keyها معتبرند.
  • مبالغ در کد محاسبه می‌شوند.
  • توزیع‌ها اندازه‌گیری می‌شوند.
  • Edge Caseها تضمین‌شده‌اند.
  • داده عادی و نامعتبر جدا هستند.
  • Generator نسخه دارد.
  • Plan Hash ذخیره می‌شود.
  • Mock API از Production جداست.
  • Dataset Quality Test وجود دارد.
  • API Key فقط در Backend نگهداری می‌شود.

پرسش‌های متداول

داده مصنوعی یا Synthetic Data چیست؟

داده‌ای ساختگی است که بر اساس Schema، توزیع و قواعد مشخص تولید می‌شود تا سناریوهای واقعی یا مرزی را شبیه‌سازی کند.

آیا هوش مصنوعی می‌تواند داده تست بسازد؟

بله. AI می‌تواند Plan، Edge Case، Fixture کوچک و متن‌های متنوع تولید کند. برای Dataset بزرگ بهتر است مدل Plan بسازد و Python داده‌ها را تولید کند.

چگونه داده تست فارسی تولید کنیم؟

از Faker با Locale فارسی مانند fa_IR، فهرست کنترل‌شده شهر و محصول و قواعد نرمال‌سازی استفاده کنید.

چگونه داده قابل تکرار بسازیم؟

برای Random و Faker یک Seed ثابت تعیین و نسخه Generator و Hash برنامه را ذخیره کنید.

Mock API چیست؟

سرویسی شبیه API واقعی است که Responseهای کنترل‌شده برای توسعه Frontend و تست Integration ارائه می‌دهد.

آیا می‌توان Mock API را از OpenAPI ساخت؟

بله. Endpoint، Request، Response و Status Codeها از OpenAPI استخراج می‌شوند و Scenarioهای معتبر بر اساس آن ساخته می‌شوند.

چرا مدل نباید هزاران ردیف را مستقیماً بسازد؟

هزینه، ناپایداری ساختار، خطای رابطه، شناسه تکراری و بازتولیدناپذیری افزایش می‌یابد.

بهترین مدل برای تولید داده تست کدام است؟

مدلی مناسب است که Requirement فارسی و Structured Output را دقیق دنبال کند. مدل‌های در دسترس را در صفحه مدل‌های درواره مقایسه کنید.

API درواره چگونه متصل می‌شود؟

در Backend، base_url را روی https://api.darvareh.ir/v1 تنظیم کنید و Requirement و Schema را برای تولید Data Plan به مدل بفرستید.

جمع‌بندی

تولید داده تست با هوش مصنوعی زمانی قابل اعتماد است که مدل و Generator قطعی وظایف جداگانه‌ای داشته باشند.

مدل هوش مصنوعی باید Requirement را درک کند، موجودیت‌ها، توزیع‌ها و Edge Caseها را در یک Plan ساختاریافته قرار دهد. Python و Faker باید بر اساس همان Plan، داده قابل تکرار تولید کنند. در پایان نیز Schema، روابط، شناسه‌ها، جمع مبالغ و توزیع‌ها باید با کد بررسی شوند.

در پروژه این مقاله یک AI Test Data Generator با Python، Faker، FastAPI، Pydantic و API درواره ساختیم. این ابزار Plan تولید می‌کند، داده فروشگاه فارسی می‌سازد، کیفیت آن را ارزیابی می‌کند و یک Mock API برای توسعه و تست ارائه می‌دهد.

برای شروع، در درواره ثبت‌نام و API Key دریافت کنید. سپس مدل مناسب را از صفحه مدل‌های درواره انتخاب کرده و Generator را ابتدا با یک Schema کوچک و چند سناریوی مشخص آزمایش کنید.

مقالات مرتبط

برای مطالعه شرایط استفاده و محدودیت‌های مسئولیت، صفحه «سلب مسئولیت» را مشاهده کنید.

Read more

اتوماسیون هوش مصنوعی چیست؟ کاربردها و آموزش ساخت AI Automation

اتوماسیون هوش مصنوعی چیست؟ کاربردها و آموزش ساخت AI Automation

اتوماسیون هوش مصنوعی با ترکیب گردش‌کارهای خودکار و مدل‌های هوش مصنوعی، پردازش متن، دسته‌بندی، استخراج اطلاعات و تصمیم‌های پیشنهادی را خودکار می‌کند. در این راهنما، معماری و ساخت نمونه عملی آن با API درواره را می‌آموزید.

Agentic Commerce چیست؟ آینده خرید با ایجنت هوش مصنوعی

Agentic Commerce چیست؟ آینده خرید با ایجنت هوش مصنوعی

Agentic Commerce شیوه‌ای جدید برای خرید اینترنتی است که در آن ایجنت هوش مصنوعی می‌تواند نیاز کاربر را بفهمد، محصولات را جست‌وجو و مقایسه کند و فرایند خرید را پیش ببرد. در این راهنما با معماری، UCP، ACP و پیاده‌سازی آن با API درواره آشنا می‌شوید.