ویژگی تصویر

معرفی کتابخانه factory-boy در پایتون

  /  پایتون   /  کتابخانه factory-boy در پایتون
بنر تبلیغاتی الف

factory-boy یک ابزار قدرتمند برای تولید داده‌های ساختگی (fixtures/fake data) در تست‌های نرم‌افزاری است. این کتابخانه الهام گرفته از factory_girl در دنیای روبی است و به سادگی قابل ترکیب با ORMهای مختلف مانند Django ORM، SQLAlchemy و یا مدل‌های ساده پایتون است. هدف اصلی factory-boy تسهیل ایجاد نمونه‌های مدل برای تست‌ها و جلوگیری از تکرار کدهای ساخت داده است.

چرا از factory-boy استفاده کنیم؟

  • تولید خودکار داده‌های معتبر برای تست‌ها
  • قابلیت تعریف الگوهای قابل‌استفاده مجدد (factories)
  • پشتیبانی از Faker برای تولید متن، ایمیل، آدرس و …
  • سازگاری با تست‌رانرهایی مثل pytest و unittest
  • حذف وابستگی به fixtures ساکن و آسان‌تر کردن نگهداری تست‌ها

نصب و راه‌اندازی

نصب ساده است و از pip قابل انجام است:

pip install factory-boy faker

این دستور factory-boy و faker را نصب می‌کند. faker برای تولید داده‌های تصادفی و واقعی‌تر بسیار مفید است.

مفاهیم پایه: Factory، build و create

هر factory یک کلاس است که نحوه ساخت یک نمونه از یک مدل را تعریف می‌کند. دو متد مهم وجود دارد:

  • build(): نمونه را در حافظه می‌سازد ولی در پایگاه‌داده ذخیره نمی‌کند.
  • create(): نمونه را می‌سازد و اگر مدل به پایگاه‌داده متصل باشد، آن را ذخیره می‌کند.

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

import factory
from faker import Faker

fake = Faker()

class User:
    def __init__(self, username, email):
        self.username = username
        self.email = email

class UserFactory(factory.Factory):
    class Meta:
        model = User

    username = factory.Sequence(lambda n: f"user{n}")
    email = factory.LazyAttribute(lambda o: f"{o.username}@example.com")

در این مثال، UserFactory نحوهٔ ساخت شیء User را تعریف می‌کند. Sequence برای تولید نام‌های یکتا استفاده شده و LazyAttribute به یک خاصیت دیگر از شیء دسترسی دارد تا ایمیل سازگار با نام کاربری تولید کند.

ادغام با Django

برای پروژه‌های Django، از DjangoModelFactory استفاده می‌شود. این کلاس کار ذخیره‌سازی در پایگاه‌داده را آسان می‌کند.

import factory
from django.contrib.auth.models import User

class DjangoUserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = User

    username = factory.Sequence(lambda n: f"user{n}")
    email = factory.LazyAttribute(lambda u: f"{u.username}@example.com")
    is_active = True

با استفاده از DjangoUserFactory.create() می‌توان یک کاربر در پایگاه‌داده ساخت. استفاده از Sequence و LazyAttribute مشابه مثال قبل است، با این تفاوت که مدل Django را هدف گرفته‌ایم.

ادغام با SQLAlchemy

import factory
from myapp.models import User as SAUser

class SAUserFactory(factory.alchemy.SQLAlchemyModelFactory):
    class Meta:
        model = SAUser
        sqlalchemy_session = db_session  # باید جلسه SQLAlchemy خود را مشخص کنید

    id = factory.Sequence(int)
    username = factory.Faker('user_name')
    email = factory.LazyAttribute(lambda u: f"{u.username}@example.com")

در SQLAlchemyModelFactory باید sqlalchemy_session تنظیم شود تا متد create بتواند شی ساخته‌شده را ذخیره کند. Faker نیز برای تولید username به کار رفته است.

ویژگی‌های پیشرفته

  • SubFactory: برای رابطه‌های خارجی (ForeignKey) از SubFactory استفاده کنید تا به صورت خودکار نمونه‌های مرتبط ساخته شوند.
  • RelatedFactory: زمانی که می‌خواهید شی فرعی به صورت جدا وابسته به شی والد ساخته شود.
  • Traits: برای تعریف حالت‌های پیش‌تعریف‌شده (مثلاً کاربر فعال/مدیر)
  • post_generation: برای اجرای کارهای بعد از ساخت شی، مثل افزودن M2M یا انجام عملیات جانبی

مثال SubFactory و post_generation

class ProfileFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Profile

    user = factory.SubFactory(DjangoUserFactory)
    bio = factory.Faker('text')

class GroupFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Group

    name = factory.Faker('word')

class UserWithGroupsFactory(DjangoUserFactory):
    @factory.post_generation
    def groups(self, create, extracted, **kwargs):
        if not create:
            return
        if extracted:
            for group in extracted:
                self.groups.add(group)
        else:
            self.groups.add(GroupFactory())

در این مثال، ProfileFactory به‌طور خودکار یک User با SubFactory می‌سازد. در UserWithGroupsFactory از post_generation برای اضافه‌کردن گروه‌ها به رابطهٔ M2M استفاده شده است؛ می‌توان گروه‌ها را هم به صورت دستی (extracted) ارسال کرد یا اجازه داد factory یک گروه بسازد.

مقایسه سریع build، create و stub

متدعملکرد
buildنمونه در حافظه ساخته می‌شود؛ ذخیره‌سازی انجام نمی‌شود
createنمونه ساخته و در پایگاه‌داده ذخیره می‌شود (در صورت پشتیبانی)
build_stubbedشی شبیه‌سازی‌شده (stub) ساخته می‌شود که معمولاً برای تست‌های سریع مناسب است و به DB وابسته نیست

نکات حرفه‌ای و بهینه‌سازی

  • از build_stubbed برای تست‌هایی که نیازی به ارتباط با DB ندارند استفاده کنید تا سرعت تست‌ها افزایش یابد.
  • برای مجموعه‌های بزرگ داده، از bulk_create در SQLAlchemy/Django و بهینه‌سازی session/transaction استفاده کنید.
  • Factoryها را در ماژول‌های جدا نگه دارید و به عنوان fixtures در pytest با scope مناسب ارایه دهید.
  • از Faker.seed(…) برای تولید داده‌های تکرارپذیر در تست‌ها استفاده کنید.
  • برای جلوگیری از تداخل، از Sequence یا استفادهٔ پیش‌شماره‌ها بهره ببرید.

مثال کامل: ترکیب factory با pytest

import pytest
from myapp.factories import DjangoUserFactory

@pytest.mark.django_db
def test_user_creation():
    user = DjangoUserFactory.create(username='alice')
    assert user.username == 'alice'
    assert user.is_active

این تست نشان می‌دهد چگونه می‌توان از factory در تست‌های pytest استفاده کرد. علامت @pytest.mark.django_db تضمین می‌کند که تست اجازهٔ دسترسی به پایگاه‌داده را دارد.

جمع‌بندی

factory-boy ابزاری مفید و منعطف برای تولید داده در تست‌های پایتون است. با پشتیبانی از Django، SQLAlchemy و مدل‌های ساده پایتون، استفاده از آن باعث خواناتر شدن تست‌ها، کاهش تکرار و مدیریت بهتر داده‌های تست می‌شود. آشنایی با مفاهیمی چون SubFactory، post_generation، LazyAttribute و build_vs_create کلید استفادهٔ مؤثر از این کتابخانه است.

برای یادگیری بیشتر به مستندات رسمی library مراجعه کنید و نمونه‌ها را در پروژهٔ خودتان امتحان کنید تا بهترین شیوه‌ها را متناسب با نیازهای تست‌تان بیابید.

آیا این مطلب برای شما مفید بود ؟

خیر
بله
موضوعات شما در انجمن: