Python Programming

آشنایی با تست‌های پایتون: راهنمای جامع برای pytest و unittest

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

درک محیط تست

فریمورک داخلی پایتون unittest که از JUnit زبان جاوا الهام گرفته است، الگوی کلاسیک xUnit را دنبال می‌کند که بسیاری از توسعه‌دهندگان با آن آشنا هستند. این فریمورک روشی ساختاریافته برای نوشتن تست‌ها فراهم می‌کند که شامل روش‌های explicit setUp و tearDown، روش‌های ادعا (assertion) و ساختار سازماندهی شده است.

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

شروع به کار با unittest

فریمورک unittest دارای ساختاری آشنا است که در آن کلاس‌های تست از unittest.TestCase ارث می‌برند. نمونه‌ای ساده اینجا آمده است:

import unittest

class Calculator:
    def add(self, a, b):
        return a + b
    
    def divide(self, a, b):
        if b == 0:
            raise ValueError("Cannot divide by zero")
        return a / b

class TestCalculator(unittest.TestCase):
    def setUp(self):
        self.calc = Calculator()
    
    def test_addition(self):
        result = self.calc.add(2, 3)
        self.assertEqual(result, 5)
    
    def test_division(self):
        result = self.calc.divide(10, 2)
        self.assertEqual(result, 5)
    
    def test_division_by_zero(self):
        with self.assertRaises(ValueError):
            self.calc.divide(10, 0)

if __name__ == '__main__':
    unittest.main()

ادغام بدون دردسر با pytest

pytest دارای نحوه نوشتاری خلاصه‌تر و ویژگی‌های قدرتمندی است. این همان مجموعه تست‌ها بازنویسی شده برای pytest است:

import pytest

class Calculator:
    def add(self, a, b):
        return a + b
    
    def divide(self, a, b):
        if b == 0:
            raise ValueError("Cannot divide by zero")
        return a / b

def test_addition():
    calc = Calculator()
    assert calc.add(2, 3) == 5

def test_division():
    calc = Calculator()
    assert calc.divide(10, 2) == 5

def test_division_by_zero():
    calc = Calculator()
    with pytest.raises(ValueError):
        calc.divide(10, 0)

# تست‌های پارامتریزه شده
@pytest.mark.parametrize("a, b, expected", [
    (1, 2, 3),
    (5, 5, 10),
    (-1, 1, 0)
])
def test_addition_parametrized(a, b, expected):
    calc = Calculator()
    assert calc.add(a, b) == expected

مقایسه ویژگی‌های کلیدی

هر دو فریمورک قابلیت‌های ضروری تست را فراهم می‌کنند، اما pytest در چندین زمینه بهتر عمل می‌کند:

فیکسچرها و تنظیمات: سیستم فیکسچر در pytest قدرتمندتر از setUp/tearDown در unittest است. فیکسچرها را می‌توان در چندین فایل تست به اشتراک گذاشت و منطق پیچیده‌ای برای تنظیمات پشتیبانی می‌کنند:

@pytest.fixture
def sample_data():
    return {
        'users': [
            {'id': 1, 'name': 'Alice'},
            {'id': 2, 'name': 'Bob'}
        ]
    }

def test_user_count(sample_data):
    assert len(sample_data['users']) == 2

پاک‌سازی و پایان‌یابی: فیکسچرها همچنین می‌توانند مدیریت پاک‌سازی را با استفاده از کلمه کلیدی yield انجام دهند که مدیریت منابع را تمیزتر می‌کند:

@pytest.fixture
def database_connection():
    conn = create_connection()
    yield conn
    conn.close()

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

pytest از چندین ویژگی پیشرفته پشتیبانی می‌کند که تست‌ها را کارآمدتر می‌کنند:

  • افزونه‌ها: اکوسیستم غنی با افزونه‌هایی مانند pytest-cov برای پوشش کد و pytest-html برای گزارش‌ها
  • نشانه‌گذاری: نشانه‌های سفارشی برای دسته‌بندی و فیلتر کردن تست‌ها
  • مُک (Mocking): پشتیبانی داخلی با افزونه pytest-mock
  • تست‌های ناهمگام: پشتیبانی طبیعی از توابع ناهمگام

ادغام و گردش کار

پروژه‌های پایتون مدرن اغلب از اجرای تست‌ها در خطوط CI/CD بهره‌مند می‌شوند. هر دو فریمورک به خوبی با ابزارهای محبوب ادغام می‌شوند:

# اجرای pytest
pytest
pytest -v
pytest --cov=src

# اجرای unittest (اگر از pytest استفاده می‌کنید)
python -m unittest discover

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

عملکرد و بهترین شیوه‌ها

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

بدون توجه به فریمورک انتخابی، این بهترین شیوه‌ها را اجرا کنید:

  • تست‌های کوچک و متمرکز بنویسید
  • از نام‌های توصیفی در تست‌ها استفاده کنید
  • منطق تنظیمات مشترک را استخراج کنید
  • از فیکسچرها برای داده‌های تست استفاده کنید
  • تست‌ها را به طور منظم در توسعه اجرا کنید

نتیجه‌گیری

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

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

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

Share: