وقتی تعداد فایل‌های ارزیابی نرم‌افزار در پروژه شما از یک یا دو عدد فراتر می‌رود، با چالش زمان و مدیریت بهینه‌سازی فرآیند تست مواجه می‌شوید. فرض کنید سیستم مدیریت کاربران و کیف پول دیجیتال شما اکنون صاحب ده‌ها سناریوی ارزیابی مختلف است. اجرای یک‌باره تمام این کدهای پایتون در زمان توسعه، نه‌تنها وقت‌گیر است، بلکه تمرکز شما را روی بخشی که در حال اصلاحش هستید، از بین می‌برد.

در این درس یاد می‌گیرید که چطور با استفاده از ابزار قدرتمند Markers یا همان نشانه‌گذارها در فریم‌ورک pytest، تست‌های خود را به صورت هوشمند دسته‌بندی کنید. این قابلیت به شما اجازه می‌دهد روی هر تابع تست یک برچسب یا تگ اختصاصی بچسبانید تا در زمان اجرای دستورات، کنترل کاملی روی فیلتر کردن آن‌ها داشته باشید.

با این تکنیک می‌توانید به ابزار ارزیابی نرم‌افزار دستور دهید که در یک لحظه فقط کدهای بخش مالی را بررسی کند و بی‌خیال بخش ثبت‌نام کاربران شود. این شیوه از مدیریت زمان اجرای تست‌ها در پروژه‌های بزرگ پایتون، سرعت توسعه و عیب‌یابی را چند برابر می‌کند و یکی از مهارت‌های حیاتی برای ورود به دنیای پروژه‌های واقعی است.

چرا اجرای یکجای تست‌ها در پروژه‌های بزرگ یک اشتباه است؟

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

در این مقیاس، اگر برای ارزیابی تغییرات کوچک در کدهای پایتون بخش کیف پول، هر بار مجبور باشید تمام تست‌های پروژه را اجرا کنید، با سه چالش جدی مواجه می‌شوید:

  • تلف شدن زمان توسعه‌دهنده: برخی از تست‌ها (مثل تست‌های متصل به دیتابیس یا سرویس‌های بیرونی) زمان‌بر هستند. منتظر ماندن برای اجرای سناریوهای نامربوط، سرعت توسعه شما را به شدت کاهش می‌دهد.
  • از بین رفتن تمرکز: وقتی در حال اصلاح یک باگ مشخص در بخش احراز هویت هستید، دیدن نتایج تست‌های مربوط به بخش‌های دیگر ترمینال شما را شلوغ و تحلیل خطا را سخت می‌کند.
  • استهلاک منابع سیستم: اجرای مداوم صدها تست سنگین روی سیستم، منابع پردازشی زیادی را بیهوده درگیر می‌کند.

اجرای یکجای تست‌ها در پروژه‌های بزرگ پایتون، مثل این است که برای بررسی کارکرد چراغ‌راهنمای یک ماشین، مجبور باشید کل موتور و قطعات خودرو را از اول باز و بسته کنید. مهندسان نرم‌افزار حرفه‌ای همیشه به دنبال راه‌حل‌هایی برای تفکیک محیط‌های ارزیابی نرم‌افزار هستند تا بتوانند دقیقاً روی بخشی که نیاز دارند تمرکز کنند. در بخش بعدی با ابزاری آشنا می‌شویم که این تفکیک را به ساده‌ترین شکل ممکن برای ما انجام می‌دهد.

آشنایی با مفهوم Markers و نشانه‌گذارهای پیش‌فرض در pytest

فریم‌ورک pytest برای حل این چالش، ابزاری به نام Markers (نشانه‌گذارها) را در اختیار ما می‌گذارد. مارکرها در واقع برچسب‌ها یا تگ‌هایی هستند که شما روی توابع تست خود می‌چسبانید. با این کار به ابزار ارزیابی نرم‌افزار می‌فهمانید که هر تست به کدام بخش از ساختار پروژه تعلق دارد یا در چه شرایطی باید اجرا شود.

پایتون به صورت پیش‌فرض چند مارکر بسیار کاربردی درون pytest دارد که بدون هیچ تنظیمات اضافی می‌توانید از آن‌ها استفاده کنید. سه مورد از مهم‌ترین نشانه‌گذارهای پیش‌فرض شامل موارد زیر هستند:

  • pytest.mark.skip: این برچسب به ابزار می‌گوید که این تست را کاملاً نادیده بگیر و اجرا نکن. این مارکر برای کدهایی که هنوز کامل نشده‌اند یا باگی دارند که فعلاً وقت حل کردنش را ندارید، فوق‌العاده است.
  • pytest.mark.skipif: نسخه هوشمندتر مارکر قبلی است. این تگ یک شرط می‌پذیرد؛ مثلاً می‌گویید «اگر سیستم‌عامل کاربر ویندوز بود، این تست را اجرا نکن».
  • pytest.mark.xfail: وقتی می‌دانید یک تست به دلیل مشکلی در کدهای پایتون فعلاً خطا می‌دهد اما می‌خواهید در گزارش‌ها ثبت شود، از این تگ استفاده می‌کنید. یعنی شما انتظار دارید این تست رد (Fail) شود.

برای استفاده از این ابزارها، کافی است آن‌ها را مانند یک دکوراتور (Decorator) بالای تابع تست خود قرار دهید. به این نمونه کد نگاه کنید:

import pytest
import sys

@pytest.mark.skip(reason="این بخش در دست تعمیر است")
def test_old_payment_system():
    pass

@pytest.mark.skipif(sys.platform == "win32", reason="روی ویندوز اجرا نمی‌شود")
def test_linux_feature():
    pass

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

ساخت نشانه‌گذارهای اختصاصی برای بخش کیف پول و کاربران

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

ساخت مارکر اختصاصی در pytest به شدت ساده است. شما می‌توانید هر نامی که دوست دارید (مثل wallet یا auth) را بعد از عبارت pytest.mark. بنویسید و آن را به عنوان دکوراتور بالای توابع تست خود بگذارید. فریم‌ورک به طور هوشمند این برچسب‌های جدید را می‌شناسد.

بیا فایل test_wallet.py را باز کنیم و کدهای پایتون آن را با مارکر اختصاصی برچسب‌گذاری کنیم:

import pytest

@pytest.mark.wallet
def test_wallet_deposit(empty_wallet):
    empty_wallet.deposit(50)
    assert empty_wallet.balance == 50

@pytest.mark.wallet
def test_wallet_insufficient_funds(wallet_with_funds):
    with pytest.raises(Exception):
        wallet_with_funds.withdraw(200)

حالا همین کار را برای فایل تست‌های کاربری یعنی test_users.py انجام می‌دهیم و برچسب auth را روی آن‌ها می‌زنیم:

import pytest

@pytest.mark.auth
def test_admin_creation(user_manager_system):
    assert user_manager_system.get_user_count() == 1

@pytest.mark.auth
def test_add_regular_user(user_manager_system):
    user_manager_system.register_user("ali")
    assert user_manager_system.get_user_count() == 2

ببین چقدر معماری پروژه تمیز و دسته‌بندی شد؛ اکنون هر تابع تست یک هویت مشخص دارد. یکی به بخش تراکنش‌ها متصل است و دیگری به بخش دسترسی‌ها.

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

فرماندهی اجرای تست‌ها در ترمینال با فیلترهای پیشرفته

حالا که کدهای پایتون را برچسب‌گذاری کردیم، نوبت به جذاب‌ترین بخش یعنی اجرای کنترل‌شده آن‌ها می‌رسد. ترمینال سیستم شما، مرکز فرماندهی ابزار ارزیابی نرم‌افزار است. برای فیلتر کردن تست‌ها بر اساس مارکرها، از سوئیچ -m مخفف marker استفاده می‌کنیم.

اگر می‌خواهید فقط و فقط سناریوهای مربوط به بخش مالی و تراکنش‌ها اجرا شوند، این دستور را در ترمینال بزنید:

pytest -m wallet

با اجرای این دستور، فریم‌ورک به سرعت فایل‌ها را اسکن می‌کند، دو تست برچسب‌خورده با wallet را اجرا کرده و سایر کدهای بخش مدیریت کاربران را نادیده می‌گیرد (در گزارش نهایی عبارت skipped یا deselect را برای آن‌ها می‌بینید).

اما قدرت واقعی این ابزار ارزیابی نرم‌افزار زمانی مشخص می‌شود که بخواهید از فیلترهای پیشرفته‌تر و ترکیب‌های منطقی استفاده کنید. فریم‌ورک pytest از کلمات کلیدی and، or و not به خوبی پشتیبانی می‌کند:

  • اجرای تست‌هایی که هر دو ویژگی را دارند: اگر تستی می‌خواهید که هم مربوط به کیف پول باشد و هم بخش دسترسی‌ها (در پروژه‌های بزرگ‌تر):
pytest -m "wallet and auth"
  • اجرای تست‌های یکی از دو بخش: اگر می‌خواهید تست‌های هر دو بخش را هم‌زمان اجرا کنید:
pytest -m "wallet or auth"
  • اجرای همه تست‌ها به جز یک بخش خاص: فرض کنید می‌خواهید تمام کدهای پروژه ارزیابی شوند اما فعلاً کاری با بخش احراز هویت ندارید:
pytest -m "not auth"

این فیلترهای پیشرفته در ترمینال دقیقاً همان ابزاری است که جلوی شلوغ شدن محیط کار شما را می‌گیرد. با این ترفند، مدیریت زمان اجرای تست‌ها در پروژه‌های بزرگ پایتون از یک کار خسته‌کننده به یک فرآیند لذت‌بخش و فوق‌العاده سریع تبدیل می‌شود. در بخش پایانی درس، یاد می‌گیریم که چطور این برچسب‌ها را به ساختار پروژه معرفی کنیم تا سیستم در مواجهه با آن‌ها دچار سردرگمی و خطا نشود.

ثبت رسمی مارکرها در فایل تنظیمات پروژه (pytest.ini)

وقتی مارکرهای اختصاصی خودتان را در کدهای پایتون اجرا می‌کنید، فریم‌ورک pytest کارش را انجام می‌دهد اما یک هشدار (Warning) در ترمینال به شما نشان می‌دهد. این ابزار ارزیابی نرم‌افزار به شما می‌گوید: «این برچسب‌های جدید مثل wallet یا auth برای من ناشناخته هستند؛ مطمئنی نام آن‌ها را اشتباه تایپ نکرده‌ای؟» برای حل این مشکل و رسمی کردن برچسب‌ها، باید آن‌ها را در فایل تنظیمات پروژه ثبت کنیم.

ثبت مارکرها جلوی خطاهای احتمالی ناشی از غلط املایی را می‌گیرد و به معماری پروژه شما ساختاری کاملاً استاندارد می‌دهد. برای این کار، یک فایل جدید به نام pytest.ini در پوشه اصلی و ریشه پروژه خود بسازید. این فایل، تنظیمات کلان فریم‌ورک را در خود نگه می‌دارد.

کدهای زیر را درون فایل pytest.ini قرار دهید:

[pytest]
markers =
    wallet: تست‌های مربوط به موجودی و تراکنش‌های کیف پول دیجیتال
    auth: تست‌های مربوط به دسترسی‌ها و سیستم مدیریت کاربران

به ساختار این فایل دقت کنید؛ ابتدا بخش [pytest] را مشخص کردیم و سپس زیرعبارت markers را آوردیم. جلوی هر برچسب، یک دونقطه گذاشتیم و یک توضیح کوتاه درباره کارکرد آن نوشتیم. این توضیحات به بقیه اعضای تیم توسعه کمک می‌کند تا فلسفه وجودی هر تگ را متوجه شوند.

اصل حرف این است که بدون ثبت رسمی مارکرها در فایل تنظیمات پروژه (pytest.ini)، مدیریت زمان اجرای تست‌ها در پروژه‌های بزرگ پایتون در تیم‌های بزرگ به یک چالش تبدیل می‌شود؛ چون اگر کسی نام یک تگ را اشتباه بنویسد، فریم‌ورک بدون هیچ خطایی آن تست را نادیده می‌گیرد و رد می‌کند. با این کار، محیط ارزیابی شما کاملاً پایدار، بدون هشدار و آماده برای فرآیندهای تست نویسی پیشرفته‌تر می‌شود.

نحوه مارک‌گذاری کل یک فایل یا یک کلاس (بجای تک‌تک توابع)

وقتی در حال توسعه کدهای پایتون در پروژه‌های واقعی هستید، متوجه می‌شوید که نوشتن دکوراتور بالای تک‌تک توابع تست، کار زمان‌بر و خسته‌کننده‌ای است. فرض کنید یک فایل تست به نام test_wallet.py دارید که شامل ده‌ها تابع تست مختلف برای ارزیابی نرم‌افزار کیف پول است. گذاشتن دکوراتور @pytest.mark.wallet بالای همه این توابع، کدهای شما را شلوغ می‌کند و احتمال خطای انسانی یا فراموشی را بالا می‌برد.

فریم‌ورک pytest برای این چالش دو راهکار بسیار هوشمندانه دارد؛ مارک‌گذاری کل فایل یا مارک‌گذاری یک کلاس به صورت یک‌جا.

اگر می‌خواهید تمام تست‌های موجود در یک فایل بدون استثنا برچسب بخورند، کافی است متغیر مخصوص pytestmark را در ابتدای فایل کدهای پایتون خود (خارج از تمام کلاس‌ها و توابع) تعریف کنید:

import pytest

# با این خط کد، تمام تست‌های این فایل برچسب wallet می‌خورند
pytestmark = pytest.mark.wallet

def test_deposit_money():
    pass

def test_withdraw_money():
    pass

اما گاهی اوقات شما تمایل دارید تست‌های خود را درون کلاس‌ها دسته‌بندی کنید. اگر یک کلاس مشخص دارید و می‌خواهید تمام متدهای تست داخل آن کلاس برچسب بخورند، می‌توانید دکوراتور مارکر را دقیقاً بالای نام کلاس قرار دهید. به این نمونه کد نگاه کنید:

import pytest

@pytest.mark.auth
class TestUserManagement:
    def test_login(self):
        pass

    def test_logout(self):
        pass

بذار ساده بگم؛ با این دو ترفند، دیگر نیازی به تکرار خسته‌کننده دکوراتورها ندارید. ابزار ارزیابی نرم‌افزار به محض ورود به فایل یا کلاس، برچسب‌ها را می‌خواند و تمام توابع زیرمجموعه را تحت پوشش قرار می‌دهد. این قابلیت، سرعت مدیریت زمان اجرای تست‌ها در پروژه‌های بزرگ پایتون را به شدت بالا می‌برد و ساختار کدهای شما را تمیز و خوانا نگه می‌دارد.

دیدن لیست تمام مارکرهای ثبت‌شده در ترمینال

در پروژه‌های بزرگ که تعداد اعضای تیم توسعه زیاد است، مارکرهای اختصاصی متعددی در فایل تنظیمات ثبت می‌شوند. بعد از مدتی، ممکن است نام دقیق برخی از این برچسب‌ها یا کاربرد واقعی آن‌ها را فراموش کنید. باز کردن مداوم فایل تنظیمات برای چک کردن نام‌ها، فرآیند کار شما را کند می‌کند و تمرکزتان را به هم می‌زند.

ترمینال سیستم شما یک دستور اختصاصی برای حل این چالش دارد. شما می‌توانید بدون باز کردن هیچ فایلی، لیست کامل تمام مارکرهای مجاز پروژه به همراه توضیحات آن‌ها را مستقیماً در محیط خط فرمان ببینید.

کافی است این دستور را در ترمینال خود اجرا کنید:

pytest --markers

با اجرای این دستور، ابزار ارزیابی نرم‌افزار لیستی از تمام مارکرهای پیش‌فرض (مثل skip و xfail) و مارکرهای اختصاصی شما (مثل wallet و auth) را به همراه توضیحات دقیقی که در فایل تنظیمات نوشته بودید، در خروجی چاپ می‌کند.

اصل حرف این است که این دستور مثل یک دفترچه راهنمای سریع برای شما و سایر توسعه‌دهندگان پروژه عمل می‌کند. با این کار، جلوی استفاده از برچسب‌های اشتباه یا تکراری در کدهای پایتون گرفته می‌شود و فرآیند فیلتر کردن تست‌ها در پروژه‌های بزرگ پایتون کاملاً یکپارچه، دقیق و بدون هشدار باقی می‌ماند. با این تکنیک، پرونده مدیریت زمان اجرای تست‌ها در پروژه‌های بزرگ پایتون را بستیم. 

در بخش بعدی، سراغ یکی از جذاب‌ترین قابلیت‌های pytest یعنی تست‌های پارامتریزه می‌رویم تا ببینیم چطور می‌توان یک تست را با ده‌ها داده مختلف به صورت خودکار تکرار کرد.