تست نویسی در پایتون چیست؟

آموزش تست نویسی در Python با assert و کتابخانه unittest

تست نویسی چیست؟

وقتی یک برنامه پایتونی می‌نویسیم، باید مطمئن شویم که برنامه در شرایط مختلف درست کار می‌کند. یکی از روش‌های مهم برای بررسی عملکرد برنامه، تست نویسی (Testing) است.

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

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

چرا تست نویسی مهم است؟

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

دستور assert در پایتون

یکی از ساده‌ترین روش‌ها برای بررسی یک شرط در پایتون، استفاده از دستور assert است.

x = 10

assert x == 10

اگر شرط درست باشد، برنامه بدون خطا ادامه پیدا می‌کند. اما اگر شرط نادرست باشد، پایتون خطای AssertionError ایجاد می‌کند.

مثال ساده با assert

def add(a, b):
    return a + b


result = add(2, 3)

assert result == 5

در این مثال بررسی می‌کنیم که نتیجه تابع add() برابر با 5 باشد.

کتابخانه unittest چیست؟

پایتون یک کتابخانه داخلی به نام unittest دارد که برای نوشتن و اجرای تست‌ها استفاده می‌شود.

برای استفاده از آن نیازی به نصب پکیج جداگانه نداریم.

import unittest

اولین تست با unittest

ابتدا یک تابع ساده ایجاد می‌کنیم:

def add(a, b):
    return a + b

سپس برای آن یک کلاس تست ایجاد می‌کنیم:

import unittest


def add(a, b):
    return a + b


class TestAdd(unittest.TestCase):

    def test_add(self):
        self.assertEqual(add(2, 3), 5)


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

متد test_add() یک تست برای تابع add() است.

TestCase چیست؟

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

class TestCalculator(unittest.TestCase):

    def test_add(self):
        self.assertEqual(2 + 3, 5)

کلاس تست باید از TestCase ارث‌بری کند.

مهم‌ترین Assertion ها در unittest

کتابخانه unittest چندین متد برای بررسی نتایج در اختیار ما قرار می‌دهد.

assertEqual

self.assertEqual(2 + 2, 4)

بررسی می‌کند که دو مقدار برابر باشند.

assertNotEqual

self.assertNotEqual(2 + 2, 5)

بررسی می‌کند که دو مقدار برابر نباشند.

assertTrue

self.assertTrue(10 > 5)

assertFalse

self.assertFalse(10 < 5)

assertIn

self.assertIn("Python", ["Python", "Java", "C++"])

assertNotIn

self.assertNotIn("PHP", ["Python", "Java", "C++"])

نوشتن چند تست برای یک تابع

import unittest


def multiply(a, b):
    return a * b


class TestMultiply(unittest.TestCase):

    def test_positive_numbers(self):
        self.assertEqual(multiply(2, 3), 6)

    def test_zero(self):
        self.assertEqual(multiply(5, 0), 0)

    def test_negative_number(self):
        self.assertEqual(multiply(-2, 3), -6)


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

در این مثال سه حالت مختلف را آزمایش کرده‌ایم.

نام‌گذاری تست‌ها

متدهای تست باید با عبارت test_ شروع شوند.

def test_add(self):
    ...


def test_subtract(self):
    ...


def test_login(self):
    ...

اگر نام متد با test_ شروع نشود، unittest آن را به‌عنوان تست شناسایی نمی‌کند.

متد setUp چیست؟

گاهی قبل از اجرای هر تست لازم است یک سری اطلاعات یا اشیا را آماده کنیم. برای این کار می‌توانیم از setUp() استفاده کنیم.

import unittest


class TestUser(unittest.TestCase):

    def setUp(self):
        self.username = "ali"

    def test_username(self):
        self.assertEqual(self.username, "ali")


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

متد setUp() قبل از اجرای هر تست اجرا می‌شود.

متد tearDown چیست؟

اگر بعد از اجرای تست‌ها نیاز به انجام عملیات پاک‌سازی داشته باشیم، می‌توانیم از tearDown() استفاده کنیم.

import unittest


class TestExample(unittest.TestCase):

    def setUp(self):
        print("شروع تست")

    def tearDown(self):
        print("پایان تست")

    def test_example(self):
        self.assertEqual(1 + 1, 2)


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

تست کردن خطاها با assertRaises

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

import unittest


def divide(a, b):
    return a / b


class TestDivide(unittest.TestCase):

    def test_zero_division(self):
        with self.assertRaises(ZeroDivisionError):
            divide(10, 0)


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

در این تست انتظار داریم تقسیم بر صفر باعث ایجاد ZeroDivisionError شود.

یک مثال کاربردی: بررسی سن کاربر

def is_adult(age):
    return age >= 18

حالا برای این تابع چند تست می‌نویسیم:

import unittest


def is_adult(age):
    return age >= 18


class TestIsAdult(unittest.TestCase):

    def test_adult(self):
        self.assertTrue(is_adult(20))

    def test_not_adult(self):
        self.assertFalse(is_adult(15))

    def test_exactly_18(self):
        self.assertTrue(is_adult(18))


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

اجرای تست‌ها در پایتون

فرض کنیم فایل تست ما نام زیر را دارد:

test_calculator.py

می‌توانیم آن را مستقیماً اجرا کنیم:

python test_calculator.py

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

python -m unittest

خروجی موفق تست

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

...
----------------------------------------------------------------------
Ran 3 tests in 0.001s

OK

عبارت OK یعنی تست‌ها با موفقیت اجرا شده‌اند.

اگر یک تست شکست بخورد چه می‌شود؟

اگر نتیجه واقعی با نتیجه مورد انتظار متفاوت باشد، unittest تست را شکست‌خورده اعلام می‌کند.

self.assertEqual(2 + 2, 5)

در این حالت تست موفق نخواهد بود و جزئیات خطا نمایش داده می‌شود.

ساختار پیشنهادی پروژه

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

my_project/
│
├── calculator.py
│
└── test_calculator.py

فایل calculator.py شامل کد اصلی است و test_calculator.py تست‌های آن را نگهداری می‌کند.

نکات مهم در تست نویسی

  • تست‌ها را کوتاه و مشخص بنویسید.
  • هر تست بهتر است یک رفتار مشخص را بررسی کند.
  • نام تست باید هدف آن را مشخص کند.
  • حالت‌های مختلف ورودی را بررسی کنید.
  • فقط حالت موفق را تست نکنید؛ خطاها را نیز بررسی کنید.
  • تست‌ها را مرتب و قابل فهم نگه دارید.

unittest یا pytest؟

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

در پروژه‌های پایتونی ابزارهای دیگری مانند pytest نیز بسیار مورد استفاده قرار می‌گیرند.

برای شروع بهتر است ابتدا مفاهیم پایه تست، assertion و ساختار تست‌ها را یاد بگیریم و سپس سراغ ابزارهای پیشرفته‌تر برویم.

تمرین‌های تست نویسی

تمرین ۱

تابعی به نام subtract(a, b) بنویسید و برای آن حداقل سه تست ایجاد کنید.

تمرین ۲

تابعی بنویسید که مشخص کند یک عدد زوج است یا فرد، سپس برای آن تست بنویسید.

تمرین ۳

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

تمرین ۴

تابع تقسیم را طوری تست کنید که مطمئن شوید تقسیم بر صفر باعث ایجاد ZeroDivisionError می‌شود.

جمع‌بندی

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

در این درس با مفاهیمی مانند assert، unittest، TestCase، setUp، tearDown و assertRaises آشنا شدیم.

در مراحل بعدی می‌توانیم سراغ ابزارهای حرفه‌ای‌تر تست نویسی مانند pytest برویم.

سوالات متداول

تست نویسی در پایتون چیست؟

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

آیا unittest نیاز به نصب دارد؟

خیر. unittest بخشی از کتابخانه استاندارد پایتون است و معمولاً نیازی به نصب جداگانه ندارد.

assert در پایتون چه کاری انجام می‌دهد؟

assert یک شرط را بررسی می‌کند و اگر شرط نادرست باشد، معمولاً خطای AssertionError ایجاد می‌کند.

آیا باید همه برنامه را تست کنیم؟

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