pytest چیست؟ معرفی پای‌تست برای تست خودکار در پایتون

معرفی

pytest یک چارچوب متن‌باز برای نوشتن و اجرای تست‌های خودکار در Python است. با استفاده از آن می‌توان رفتار توابع، کلاس‌ها، APIها و بخش‌های مختلف یک برنامه را بررسی کرد و مطمئن شد تغییرات جدید، رفتارهای قبلی را ناخواسته خراب نکرده‌اند.

pytest فقط برای Unit Testing (تست واحد) نیست. می‌توان از آن برای تست واحد، تست یکپارچگی، تست API و سازمان‌دهی تست‌های پیچیده‌تر یک پروژه Python نیز استفاده کرد.

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

نوشتن تست با assert

در pytest معمولاً از دستور استاندارد assert در Python برای بررسی نتیجه استفاده می‌شود.

برای مثال، اگر خروجی یک تابع باید برابر عدد مشخصی باشد، می‌توان همان مقایسه را مستقیماً در assert نوشت. وقتی تست شکست می‌خورد، pytest عبارت موردبررسی و مقادیر مرتبط را نمایش می‌دهد تا پیدا کردن علت خطا ساده‌تر شود.

این قابلیت به دلیل بازنویسی و بررسی دقیق عبارت‌های assert در زمان اجرای تست فراهم می‌شود. بنابراین در بسیاری از تست‌ها نیازی به متدهایی مانند assertEqual یا assertTrue نیست.

فیکسچر در pytest چیست؟

Fixture (فیکسچر) روشی برای آماده‌کردن داده یا محیط موردنیاز تست و در صورت نیاز پاک‌سازی آن پس از پایان تست است.

برای مثال، فیکسچر می‌تواند یک فایل موقت بسازد، داده آزمایشی ایجاد کند، اتصال موردنیاز یک تست را آماده کند یا نمونه‌ای از یک شیء را در اختیار چند تست قرار دهد.

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

اشتراک فیکسچر میان فایل‌های تست

فیکسچرهای مشترک را می‌توان در فایل conftest.py قرار داد. pytest این فایل را به‌صورت خودکار شناسایی می‌کند و تست‌های موجود در محدوده مربوط می‌توانند بدون واردکردن مستقیم آن، از فیکسچرها استفاده کنند.

این روش در پروژه‌های بزرگ کمک می‌کند آماده‌سازی مشترک تست‌ها در یک محل مشخص نگهداری شود.

پارامتری‌سازی تست‌ها

Parametrization (پارامتری‌سازی) اجازه می‌دهد یک تست را با چند مجموعه داده مختلف اجرا کنید، بدون اینکه برای هر ورودی یک تابع تست جدا بنویسید.

برای نمونه، اگر یک تابع محاسباتی باید برای چند ورودی مختلف بررسی شود، می‌توان با pytest.mark.parametrize ورودی‌ها و خروجی‌های موردانتظار را مشخص کرد.

pytest سپس همان تست را برای هر مجموعه داده جداگانه اجرا و نتیجه آن را گزارش می‌کند.

شناسایی خودکار تست‌ها

pytest می‌تواند فایل‌ها، کلاس‌ها و توابع تست را بر اساس الگوهای نام‌گذاری شناسایی کند.

به‌طور پیش‌فرض، فایل‌هایی با الگوهایی مانند test_*.py و *_test.py و توابعی که نام آن‌ها با test شروع می‌شود، در فرایند جمع‌آوری تست‌ها قرار می‌گیرند.

به همین دلیل در پروژه‌های معمول لازم نیست فهرست همه تست‌ها را به‌صورت دستی به pytest معرفی کنید.

انتخاب و دسته‌بندی تست‌ها

برای پروژه‌های بزرگ‌تر می‌توان فقط بخشی از تست‌ها را از خط فرمان اجرا کرد.

همچنین Marker (نشانگر) در pytest اجازه می‌دهد تست‌ها را با برچسب‌هایی مانند تست کند، تست یکپارچگی یا تست مخصوص یک محیط مشخص دسته‌بندی کنید.

نشانگرهای داخلی مانند skip، skipif و xfail نیز برای ردکردن موقت تست یا ثبت شکست موردانتظار استفاده می‌شوند.

چه زمانی pytest انتخاب مناسبی است؟

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

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

تفاوت pytest و unittest

unittest چارچوب تست داخلی کتابخانه استاندارد Python است و بدون نصب بسته جداگانه در دسترس قرار دارد.

در unittest معمولاً تست‌ها در کلاس‌های مبتنی بر TestCase نوشته می‌شوند و از متدهایی مانند assertEqual استفاده می‌شود. pytest در بسیاری از سناریوها امکان نوشتن تست با تابع ساده و assert معمولی را فراهم می‌کند.

pytest همچنین سیستم فیکسچر و پارامتری‌سازی گسترده‌ای دارد.

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

pytest و تست API

pytest خودش ابزار ارسال درخواست HTTP نیست. برای تست API معمولاً از کتابخانه‌ای مانند requests یا HTTPX برای ارسال درخواست استفاده می‌شود و pytest مسئول سازمان‌دهی اجرای تست، آماده‌کردن داده‌ها و بررسی نتیجه است.

برای مثال، می‌توانید درخواست را با یک کلاینت HTTP ارسال کنید و سپس با assert وضعیت پاسخ یا داده برگشتی را بررسی کنید.

به همین دلیل عبارت «تست API با pytest» معمولاً به معنی استفاده از pytest به‌عنوان چارچوب اجرای تست در کنار یک کلاینت API است.

pytest و تست مرورگر

pytest ابزار کنترل مرورگر نیست. برای بازکردن صفحه، کلیک روی عناصر و تعامل با مرورگر باید از ابزاری مانند Playwright یا Selenium استفاده شود.

pytest می‌تواند اجرای این تست‌ها، فیکسچرها، داده‌های آزمایشی و گزارش نتیجه را سازمان‌دهی کند، اما نقش Playwright یا Selenium را جایگزین نمی‌کند.

افزونه‌های pytest

یکی از ویژگی‌های مهم pytest معماری افزونه‌پذیر آن است. افزونه‌ها می‌توانند قابلیت‌های جدیدی به اجرای تست اضافه کنند.

سنجش پوشش کد

برای بررسی Code Coverage (پوشش کد) معمولاً از افزونه pytest-cov استفاده می‌شود.

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

اجرای موازی تست‌ها

برای اجرای تست‌ها روی چند فرایند یا پردازنده می‌توان از افزونه‌هایی مانند pytest-xdist استفاده کرد.

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

نقطه شروع عملی

نخستین تست را بنویسید.

فایلی با نام test_sample.py بسازید و در آن یک تابع تست تعریف کنید:

def test_sum(): assert sum([1, 2]) == 3

سپس در پوشه پروژه دستور زیر را اجرا کنید:

pytest

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

از تست واقعی شروع کنید.

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

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

ویژگی‌های کلیدی

برای آشنایی بهتر با این ابزار، توجه به موارد زیر می‌تواند مفید باشد.

  • نوشتن تست با ساختار ساده

    بسیاری از تست‌ها را می‌توان با توابع معمولی Python و عبارت‌های استاندارد assert نوشت و بدون ساخت کلاس‌های مخصوص تست اجرا کرد.

  • گزارش دقیق شکست assert

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

  • فیکسچر برای آماده‌سازی تست

    فیکسچرها داده و محیط موردنیاز تست را آماده می‌کنند و می‌توانند عملیات پاک‌سازی پس از پایان تست را نیز انجام دهند.

  • اجرای پارامتری تست‌ها

    با پارامتری‌سازی می‌توان یک تست را با چند مجموعه ورودی و خروجی موردانتظار اجرا کرد و از تکرار توابع مشابه جلوگیری کرد.

  • افزونه‌های قابل‌گسترش

    قابلیت‌هایی مانند پوشش کد، اجرای موازی و یکپارچه‌سازی با ابزارهای دیگر را می‌توان از طریق افزونه‌های pytest به جریان تست اضافه کرد.

  • انتخاب دقیق تست‌ها در خط فرمان

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

  • اجرای سریع نخستین تست

    pytest بر اساس الگوهای نام‌گذاری، فایل‌ها و توابع تست را پیدا می‌کند و مجموعه تست را بدون تعریف دستی همه موارد اجرا می‌کند.

کاربردها

موارد زیر تصویری کلی از این بخش برای این ابزار ارائه می‌کنند.

  • تست واحد منطق کسب‌وکار

    توابع و کلاس‌های مستقل را با ورودی‌های مشخص آزمایش کنید و نتیجه واقعی را با رفتار موردانتظار مقایسه کنید.

  • تست API‌های بک‌اند Python

    درخواست‌ها را با یک کلاینت HTTP مانند requests یا HTTPX ارسال کنید و با pytest وضعیت پاسخ، ساختار داده و قواعد موردانتظار API را بررسی کنید.

  • جلوگیری از بازگشت باگ

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

  • تست یکپارچگی

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

  • اجرای تست در یکپارچه‌سازی مستمر

    مجموعه تست pytest را در خط لوله Continuous Integration (یکپارچه‌سازی مستمر یا CI) اجرا کنید تا تغییرات کد پیش از ادغام یا انتشار به‌صورت خودکار بررسی شوند.

  • سازمان‌دهی تست‌های مرورگر

    تست‌های Playwright یا Selenium نوشته‌شده با Python را با pytest اجرا کنید و آماده‌سازی مرورگر، داده آزمایشی و گزارش نتیجه را از طریق فیکسچرها مدیریت کنید.

پرسش‌های رایج درباره پای‌تست

در این بخش، به تعدادی از پرسش‌های رایج درباره این ابزار پاسخ داده شده است.

آیا برای یادگیری پای‌تست باید Python بلد باشم؟

بله. بهتر است پیش از شروع با تابع، شرط، استثنا، ماژول و اجرای برنامه‌های Python آشنا باشید. لازم نیست Python را در سطح پیشرفته بدانید، اما بدون درک کدی که آزمایش می‌کنید نوشتن تست معنی‌دار دشوار است.

تفاوت pytest با unittest چیست؟

unittest بخشی از کتابخانه استاندارد Python است و معمولاً ساختاری مبتنی بر کلاس و TestCase دارد. pytest امکان نوشتن تست‌های ساده با تابع و assert معمولی را فراهم می‌کند و سیستم فیکسچر و پارامتری‌سازی گسترده‌ای دارد. هر دو برای تست Python قابل استفاده هستند و انتخاب به ساختار پروژه و ترجیح تیم بستگی دارد.

آیا پای‌تست فقط برای تست واحد است؟

از pytest می‌توان برای تست واحد، تست یکپارچگی، تست API و سازمان‌دهی انواع دیگر تست‌های Python استفاده کرد. نوع تست بیشتر به محدوده‌ای که بررسی می‌کنید و وابستگی‌های درگیر بستگی دارد، نه فقط به ابزار اجرا.

فیکسچر در pytest چه مسئله‌ای را حل می‌کند؟

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

آیا پای‌تست جایگزین Playwright یا Selenium است؟

خیر. pytest چارچوب اجرای تست Python است، اما Playwright و Selenium ابزار تعامل با مرورگر هستند. در پروژه‌های Python می‌توان Playwright یا Selenium را برای کنترل مرورگر و pytest را برای سازمان‌دهی و اجرای تست‌ها در کنار هم استفاده کرد.

آیا pytest خودش پوشش کد را اندازه می‌گیرد؟

پوشش کد جزو قابلیت‌های اصلی هسته pytest نیست. برای این کار معمولا از افزونه pytest-cov در کنار pytest استفاده می‌شود.

آموزش‌های مرتبط در فرادرس

منابع پیشنهادی

برچسب‌ها و کلیدواژه‌ها