اگر فکر کردی تست‌ نویسی فقط برای اینه که به کارفرما ثابت کنی کدت کار میکنه، سخت در اشتباهی. تست ‌نویسی یعنی وقتی ساعت ۳ صبح یک فیچر جدید به پروژه اضافه می‌کنی، با خیال راحت دکمه‌ی Deploy رو بزنی و بدونی که کد های قبلی رو به فنا ندادی. در FastAPI، چون با Async سروکار داریم، تست ‌نویسی یک لول سخت‌ تر و البته لذت‌بخش ‌تره.

1.جادوی conftest.py: اتاق فرمان تست‌های شما

توی پروژه‌های بزرگ، نباید تنظیمات تست رو توی تک ‌تک فایل‌ها کپی کنی. PyTest فایلی به نام conftest.py رو میشناسه که مثل "مرکز مدیریت بحران" عمل میکنه. تمام Fixtures (آماده‌سازها) باید اینجا باشن.

چرا Fixture؟

فرض کن برای هر تست نیاز به یک دیتابیس تمیز داری. به جای اینکه توی هر تابع بنویسی "دیتابیس رو بساز"، یک فیچر تعریف میکنی که قبل از شروع تست اجرا بشه و بعد از اتمام، محیط رو تمیز کنه.

۲. تله ‌ی مرگبار: دیتابیس اشتراکی

بزرگترین اشتباه اینه که تست‌ها رو روی همون دیتابیسی اجرا کنی که کد های اصلیت دارن ازش استفاده می‌کنن. سناریو: تست شماره ۱ یوزر admin رو میسازه. تست شماره ۲ میخواد چک کنه که آیا لیست یوزرها خالیه یا نه. چون تست ۱ دیتابیس رو کثیف کرده، تست ۲ شکست میخوره.

راه حل حرفه‌ای (Database Transaction): بهترین راه اینه که هر تست رو داخل یک Transaction دیتابیس بذاری. تست شروع می‌شه، دیتا رو می‌سازه، نتیجه رو چک میکنه و در آخر کلاً Rollback میکنه. انگار که اصلاً هیچ اتفاقی نیفتاده!

@pytest.fixture(scope="function")
async def db_session():
    # ایجاد یک کانتکست دیتابیس جدید برای هر تست
    session = SessionLocal()
    try:
        yield session
    finally:
        await session.close() # بستن اتصال برای جلوگیری از پر شدن استخر (Pool)

۳. نفوذ به قلب FastAPI با Dependency Overrides

این بخش، همون جاییه که FastAPI از رقباش جلو میزنه. فرض کن یک سیستم داری که برای احراز هویت به یک سرور خارجی (مثلاً Google Auth) وصل میشه. موقع تست که نمیتونی یوزر رو بفرستی سایت گوگل!

FastAPI بهت اجازه میده وسط تست، وابستگی ‌ها رو عوض کنی. به این کد نگاه کن:

from main import app, get_db

# تعریف دیتابیس مخصوص تست
async def override_get_db():
    try:
        db = TestSessionLocal()
        yield db
    finally:
        await db.close()

# تزریق وابستگی تست به جای واقعی
app.dependency_overrides[get_db] = override_get_db

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

۴. غول مرحله آخر: تست توابع Async

توابع معمولی پایتون رو راحت صدا میزنی، اما توابع async نیاز به یک Event Loop دارن. اگر PyTest رو همین طوری اجرا کنی، بهت فحش میده! باید از پلاگین pytest-asyncio استفاده کنی و بهش بفهمونی که این تست قراره توی دنیای ناهمگام اجرا بشه.

اشتباه رایج در تست‌های Async:

خیلیا یادشون میره که کلاینت تست هم باید Async باشه. اگر از requests استفاده کنی، کل برنامه بلاک می‌شه. حتماً از HTTPX استفاده کن:

@pytest.mark.asyncio
async def test_read_main():
    async with AsyncClient(app=app, base_url="http://test") as ac:
        resp ac.get("/")
    assert response.status_code == 200

۵. تست فاجعه (Testing for Failures)

یک تستر حرفه‌ای فقط دنبال "حالت‌های خوب" نیست. تو باید تست کنی که اگر کاربر پسورد اشتباه زد، سرور واقعاً ۴۰۱ میده یا نه؟ اگر دیتابیس قطع بود، سرور ۵۰۰ می‌ده یا مدیریت شده رفتار می‌کنه؟

نکته طلایی: همیشه تستی بنویس که انتظار داری "شکست" بخوره. اگر کد مخربی زدی و تستت باز هم سبز موند، یعنی اون تست عملاً بی فایده‌ ست.

۶. بررسی پوشش کد (Coverage)

در نهایت، از کجا بفهمیم چند درصد از کد هامون تست شده؟ با استفاده از ابزار pytest-cov. این ابزار بهت یک گزارش میده و میگه: "فلان خط از فلان تابع هنوز هیچ تستی سراغش نرفته!".

فرمان اجرا: pytest --cov=app tests/

این دستور مثل یک نورافکن، نقاط تاریک و بدون تست پروژه ‌ات رو بهت نشون میده.

نتیجه‌گیری نهایی

تست‌نویسی در FastAPI یعنی:

  • ایزوله کردن محیط (دیتابیس تست جدا).

  • استفاده از کلاینت‌های ناهمگام (HTTPX).

  • دستکاری وابستگی‌ها (Dependency Injection Overriding).

  • پوشش حداکثری (Coverage).

یادت باشه، برنامه‌نویسی که تست نمی‌نویسه، مثل جراحی می‌مونه که با دست نشسته می‌ره اتاق عمل؛ شاید مریض زنده بمونه، اما ریسک عفونت وحشتناکه!