راهنمای کامل تستنویسی در پایتون با pytest
با بزرگتر شدن پروژههای نرمافزاری، اطمینان از صحت عملکرد بخشهای مختلف کد به یکی از مهمترین دغدغههای برنامهنویسان تبدیل میشود. در پروژههای کوچک شاید بتوان با چند اجرای دستی از درستی کد مطمئن شد، اما در پروژههای واقعی و در حال توسعه، این روش نه قابل اعتماد است و نه مقیاسپذیر.
تستنویسی به ما کمک میکند رفتار کد را به صورت خودکار بررسی کنیم و مطمئن شویم تغییرات جدید باعث خراب شدن بخشهای قبلی نشدهاند. در دنیای پایتون ابزارهای مختلفی برای تستنویسی وجود دارد، اما pytest به دلیل سادگی، قدرت بالا و انعطافپذیری، به یکی از محبوبترین انتخابها در میان برنامهنویسان تبدیل شده است.
در این مقاله قصد داریم به صورت جامع و عمیق با pytest آشنا شویم. از مفاهیم پایه تستنویسی گرفته تا نوشتن تستهای حرفهای برای پروژههای واقعی. هدف این است که پس از مطالعه این راهنما بتوانید با اطمینان از pytest در پروژههای شخصی و حرفهای خود استفاده کنید.
تستنویسی چیست و چرا اهمیت دارد؟
تستنویسی به معنای نوشتن کدهایی است که رفتار کدهای دیگر را بررسی میکنند. این تستها مشخص میکنند که یک تابع، کلاس یا ماژول دقیقاً همان کاری را انجام میدهد که انتظار داریم.
اهمیت تستنویسی فقط به پیدا کردن باگ محدود نمیشود. تستها به عنوان مستند زنده پروژه عمل میکنند و به برنامهنویس نشان میدهند کد چگونه باید رفتار کند. همچنین زمانی که قصد ریفکتور کردن یا اضافه کردن قابلیت جدید دارید، وجود تستها باعث میشود با خیال راحت تغییرات را اعمال کنید.
در پروژههای تیمی، تستنویسی نقش مهمی در جلوگیری از بروز خطاهای ناخواسته ایفا میکند و کیفیت کلی کد را افزایش میدهد.
چرا pytest انتخاب مناسبی است؟
pytest نسبت به ابزارهای قدیمیتر مانند unittest چند مزیت مهم دارد. نوشتن تستها در pytest بسیار سادهتر و خواناتر است و نیاز به ساختارهای پیچیده ندارد. همچنین pytest امکانات پیشرفتهای مانند فیکسچرها، پارامتریسازی تستها و گزارش خطای دقیق را در اختیار شما قرار میدهد.
یکی دیگر از مزایای pytest اکوسیستم غنی آن است. پلاگینهای متنوعی برای pytest وجود دارد که امکان پوششدهی کد، تست موازی، تست رابط وب و بسیاری قابلیتهای دیگر را فراهم میکنند.
نصب pytest
نصب pytest بسیار ساده است و معمولاً از طریق pip انجام میشود. بهتر است pytest را داخل محیط مجازی پروژه نصب کنید.
pip install pytest
پس از نصب میتوانید با دستور زیر از نصب صحیح آن مطمئن شوید:
pytest --version
اولین تست با pytest
pytest برای شناسایی تستها از یک قرارداد نامگذاری ساده استفاده میکند. فایلهای تست باید با test_ شروع شوند یا به test ختم شوند. همچنین توابع تست باید با test شروع شوند.
یک مثال ساده:
def add(a, b):
return a + b
def test_add():
result = add(2, 3)
assert result == 5
در این مثال از دستور assert برای بررسی نتیجه استفاده شده است. اگر شرط برقرار باشد تست موفق است و در غیر این صورت تست شکست میخورد.
برای اجرای تستها کافی است در ترمینال دستور زیر را اجرا کنید:
pytest
مفهوم assert در pytest
در pytest از دستور assert پایتون استفاده میشود و نیازی به متدهای خاص نیست. این موضوع باعث میشود تستها بسیار خوانا و نزدیک به زبان طبیعی باشند.
مثالهای مختلف استفاده از assert:
assert x == 10
assert x != 0
assert "py" in "pytest"
assert len(items) > 0
pytest در صورت شکست تست، گزارش دقیقی از مقدارها ارائه میدهد که کار دیباگ را سادهتر میکند.
سازماندهی تستها در پروژه
در پروژههای واقعی معمولاً یک پوشه جداگانه برای تستها در نظر گرفته میشود. ساختار رایج به شکل زیر است:
project/
│
├── app/
│ ├── calculator.py
│
├── tests/
│ ├── test_calculator.py
این ساختار باعث میشود تستها از کد اصلی جدا باشند و مدیریت آنها سادهتر شود.
فیکسچرها در pytest
فیکسچرها یکی از مهمترین قابلیتهای pytest هستند. فیکسچرها برای آمادهسازی دادهها یا منابع مشترک قبل از اجرای تستها استفاده میشوند.
مثال ساده از فیکسچر:
import pytest
@pytest.fixture
def sample_data():
return [1, 2, 3, 4, 5]
def test_sum(sample_data):
assert sum(sample_data) == 15
در این مثال فیکسچر sample_data به صورت خودکار به تابع تست تزریق میشود.
محدوده فیکسچرها
فیکسچرها میتوانند محدودههای مختلفی داشته باشند. محدوده تعیین میکند فیکسچر چند بار ساخته شود.
محدودههای رایج عبارتاند از:
- function
- class
- module
- session
مثال:
@pytest.fixture(scope="module")
def config():
return {"debug": True}
پارامتریسازی تستها
گاهی لازم است یک تست را با ورودیهای مختلف اجرا کنیم. pytest این امکان را با پارامتریسازی فراهم میکند.
import pytest
@pytest.mark.parametrize("a,b,result", [
(1, 2, 3),
(3, 4, 7),
(5, 5, 10),
])
def test_add(a, b, result):
assert a + b == result
این روش باعث کاهش تکرار کد و افزایش پوشش تستها میشود.
تست استثناها
برای بررسی اینکه کد در شرایط خاص خطا تولید میکند میتوان از raises استفاده کرد.
import pytest
def divide(a, b):
if b == 0:
raise ValueError("division by zero")
return a / b
def test_divide_by_zero():
with pytest.raises(ValueError):
divide(10, 0)
مارکرها در pytest
مارکرها برای دستهبندی تستها استفاده میشوند. این قابلیت به شما اجازه میدهد فقط بخشی از تستها را اجرا کنید.
مثال:
import pytest
@pytest.mark.slow
def test_heavy_process():
assert True
اجرای تستهای دارای مارکر خاص:
pytest -m slow
تستهای شکستخورده و xfail
گاهی میدانید که یک تست فعلاً شکست میخورد. در این حالت میتوانید از xfail استفاده کنید.
import pytest
@pytest.mark.xfail
def test_future_feature():
assert False
پوششدهی کد با pytest
پوششدهی کد نشان میدهد چه درصدی از کد توسط تستها اجرا شده است. برای این کار از پلاگین pytest-cov استفاده میشود.
pip install pytest-cov
اجرای تست با پوششدهی:
pytest --cov=app
تست در پروژههای واقعی
در پروژههای واقعی معمولاً تستها برای بخشهای مختلف نوشته میشوند. تست توابع ساده، تست کلاسها، تست لایه منطق و حتی تست ارتباط با دیتابیس یا API.
نکته مهم این است که تستها باید مستقل باشند و به نتیجه تستهای دیگر وابسته نشوند.
اشتباهات رایج در تستنویسی
یکی از اشتباهات رایج نوشتن تستهای بیش از حد وابسته به پیادهسازی است. تستها باید رفتار را بررسی کنند نه جزئیات داخلی کد را.
اشتباه دیگر نادیده گرفتن تستها در زمان توسعه است. بهتر است تستنویسی همزمان با توسعه انجام شود نه در پایان پروژه.
سخن پایانی
pytest یکی از قدرتمندترین ابزارهای تستنویسی در اکوسیستم پایتون است. این ابزار با سادگی در نوشتن تستها و امکانات پیشرفتهای که ارائه میدهد، به شما کمک میکند کدی پایدار، قابل اعتماد و قابل توسعه بنویسید.
اگر به عنوان یک برنامهنویس پایتون به دنبال افزایش کیفیت پروژههای خود هستید، یادگیری و استفاده از pytest یک ضرورت است نه یک انتخاب. با تمرین و استفاده مداوم از این ابزار، تستنویسی به بخش طبیعی و جدانشدنی فرایند توسعه شما تبدیل خواهد شد.