🐍 کتابخانه مرجع پایتون

🧪 تست نویسی با unittest در پایتون

تست نویسی یکی از اصول مهم در توسعه نرم‌افزار است که به شما کمک می‌کند کیفیت کد خود را تضمین کنید و از بروز خطاهای ناخواسته جلوگیری کنید. ماژول unittest یک فریم‌ورک استاندارد و قدرتمند در پایتون است که امکانات کامل برای نوشتن و اجرای تست‌های واحد (Unit Tests) را فراهم می‌کند.

🔹 نصب و import

# unittest جزء کتابخانه استاندارد پایتون است و نیاز به نصب جداگانه ندارد
import unittest
import math

print(f"Unittest version: {unittest.__version__}")

🔹 اولین تست

import unittest

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

class TestMath(unittest.TestCase):
    def test_add(self):
        self.assertEqual(add(2, 3), 5)
        self.assertEqual(add(-1, 1), 0)
        self.assertEqual(add(0, 0), 0)

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

🔹 متدهای مهم assert

متد توضیح
assertEqual(a, b) بررسی برابری a و b
assertNotEqual(a, b) بررسی نابرابری a و b
assertTrue(x) بررسی درست بودن x
assertFalse(x) بررسی نادرست بودن x
assertIs(a, b) بررسی یکسان بودن شیء
assertIsNone(x) بررسی None بودن x
assertIn(a, b) بررسی عضویت a در b
assertRaises(Error, func) بررسی اینکه تابع خطای مشخصی را پرتاب کند
assertAlmostEqual(a, b) بررسی برابری تقریبی اعداد اعشاری

🔹 تست با assertEqual و assertNotEqual

import unittest

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

def divide(a, b):
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b

class TestMath(unittest.TestCase):
    def test_multiply(self):
        self.assertEqual(multiply(2, 3), 6)
        self.assertEqual(multiply(-2, 3), -6)
        self.assertEqual(multiply(0, 5), 0)
        self.assertNotEqual(multiply(2, 3), 5)
    
    def test_divide(self):
        self.assertEqual(divide(10, 2), 5.0)
        self.assertAlmostEqual(divide(1, 3), 0.3333333, places=6)

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

🔹 تست با assertTrue و assertFalse

import unittest

def is_even(n):
    return n % 2 == 0

def is_positive(n):
    return n > 0

class TestConditions(unittest.TestCase):
    def test_is_even(self):
        self.assertTrue(is_even(4))
        self.assertFalse(is_even(5))
        self.assertTrue(is_even(0))
    
    def test_is_positive(self):
        self.assertTrue(is_positive(10))
        self.assertFalse(is_positive(-5))
        self.assertFalse(is_positive(0))

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

🔹 تست با assertRaises

import unittest

def divide(a, b):
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b

def get_element(lst, index):
    if index < 0 or index >= len(lst):
        raise IndexError("Index out of range")
    return lst[index]

class TestExceptions(unittest.TestCase):
    def test_divide_by_zero(self):
        with self.assertRaises(ValueError):
            divide(10, 0)
        
        with self.assertRaisesRegex(ValueError, "Cannot divide by zero"):
            divide(10, 0)
    
    def test_index_error(self):
        with self.assertRaises(IndexError):
            get_element([1, 2, 3], 5)

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

🔹 setUp و tearDown

import unittest

class TestWithSetup(unittest.TestCase):
    def setUp(self):
        """قبل از هر تست اجرا می‌شود"""
        self.data = [1, 2, 3, 4, 5]
        print("\n[setUp] Data initialized")
    
    def tearDown(self):
        """بعد از هر تست اجرا می‌شود"""
        self.data = None
        print("[tearDown] Data cleaned up")
    
    def test_sum(self):
        self.assertEqual(sum(self.data), 15)
    
    def test_max(self):
        self.assertEqual(max(self.data), 5)
    
    def test_min(self):
        self.assertEqual(min(self.data), 1)

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

🔹 setUpClass و tearDownClass

import unittest

class TestClassLevel(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        """یک بار قبل از همه تست‌ها اجرا می‌شود"""
        cls.shared_data = [1, 2, 3, 4, 5]
        print("\n[setUpClass] Shared data created")
    
    @classmethod
    def tearDownClass(cls):
        """یک بار بعد از همه تست‌ها اجرا می‌شود"""
        cls.shared_data = None
        print("[tearDownClass] Shared data cleaned up")
    
    def test_sum(self):
        self.assertEqual(sum(self.shared_data), 15)
    
    def test_length(self):
        self.assertEqual(len(self.shared_data), 5)

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

🔹 تست با دیتاست (Parameterized Tests)

import unittest

def celsius_to_fahrenheit(celsius):
    return (celsius * 9/5) + 32

class TestConversion(unittest.TestCase):
    # تست با داده‌های متعدد
    def test_celsius_to_fahrenheit(self):
        test_cases = [
            (0, 32.0),
            (100, 212.0),
            (-40, -40.0),
            (37, 98.6)
        ]
        for celsius, expected in test_cases:
            with self.subTest(celsius=celsius):
                self.assertEqual(celsius_to_fahrenheit(celsius), expected)

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

🔹 اسکیپ کردن تست‌ها (Skip)

import unittest
import sys

class TestSkip(unittest.TestCase):
    def test_always_passes(self):
        self.assertTrue(True)
    
    @unittest.skip("This test is skipped for demonstration")
    def test_always_skipped(self):
        self.assertTrue(False)
    
    @unittest.skipIf(sys.platform == "win32", "Skipped on Windows")
    def test_skip_if_windows(self):
        self.assertTrue(True)
    
    @unittest.skipUnless(sys.platform == "linux", "Skipped unless Linux")
    def test_skip_unless_linux(self):
        self.assertTrue(True)

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

🔹 Mocking با unittest.mock

import unittest
from unittest.mock import Mock, patch, MagicMock

# کلاس ساده برای تست
class UserService:
    def get_user(self, user_id):
        # فرضاً این متد به دیتابیس متصل می‌شود
        return {"id": user_id, "name": "Saba"}

class TestMocking(unittest.TestCase):
    def test_mock_object(self):
        # ایجاد Mock
        mock_service = Mock(spec=UserService)
        mock_service.get_user.return_value = {"id": 1, "name": "Mock User"}
        
        # تست با Mock
        result = mock_service.get_user(1)
        self.assertEqual(result["name"], "Mock User")
        mock_service.get_user.assert_called_once_with(1)
    
    @patch('__main__.UserService')
    def test_patch_class(self, MockUserService):
        # تنظیم Mock
        mock_instance = MockUserService.return_value
        mock_instance.get_user.return_value = {"id": 1, "name": "Patched User"}
        
        # تست
        service = UserService()
        result = service.get_user(1)
        self.assertEqual(result["name"], "Patched User")

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

🔹 مثال جامع: تست یک سیستم مدیریت کاربران

import unittest
from unittest.mock import patch, MagicMock

class UserManager:
    def __init__(self):
        self.users = {}
        self.next_id = 1
    
    def add_user(self, name, email):
        if not name or not email:
            raise ValueError("Name and email are required")
        if '@' not in email:
            raise ValueError("Invalid email format")
        
        user_id = self.next_id
        self.users[user_id] = {"id": user_id, "name": name, "email": email}
        self.next_id += 1
        return user_id
    
    def get_user(self, user_id):
        if user_id not in self.users:
            raise KeyError(f"User {user_id} not found")
        return self.users[user_id]
    
    def update_user(self, user_id, **kwargs):
        if user_id not in self.users:
            raise KeyError(f"User {user_id} not found")
        for key, value in kwargs.items():
            if key in ["name", "email"]:
                self.users[user_id][key] = value
        return self.users[user_id]
    
    def delete_user(self, user_id):
        if user_id not in self.users:
            raise KeyError(f"User {user_id} not found")
        return self.users.pop(user_id)

class TestUserManager(unittest.TestCase):
    def setUp(self):
        """ایجاد یک نمونه جدید از UserManager قبل از هر تست"""
        self.manager = UserManager()
        self.test_user_id = self.manager.add_user("Saba", "[email protected]")
    
    def test_add_user(self):
        # تست افزودن کاربر معتبر
        user_id = self.manager.add_user("Ali", "[email protected]")
        user = self.manager.get_user(user_id)
        self.assertEqual(user["name"], "Ali")
        self.assertEqual(user["email"], "[email protected]")
        
        # تست خطاهای افزودن کاربر
        with self.assertRaises(ValueError):
            self.manager.add_user("", "[email protected]")
        with self.assertRaises(ValueError):
            self.manager.add_user("Test", "invalid-email")
    
    def test_get_user(self):
        # تست دریافت کاربر معتبر
        user = self.manager.get_user(self.test_user_id)
        self.assertEqual(user["name"], "Saba")
        self.assertEqual(user["email"], "[email protected]")
        
        # تست دریافت کاربر ناموجود
        with self.assertRaises(KeyError):
            self.manager.get_user(999)
    
    def test_update_user(self):
        # تست به‌روزرسانی کاربر
        updated = self.manager.update_user(self.test_user_id, name="Saba Ahmadi", email="[email protected]")
        self.assertEqual(updated["name"], "Saba Ahmadi")
        self.assertEqual(updated["email"], "[email protected]")
        
        # تست به‌روزرسانی کاربر ناموجود
        with self.assertRaises(KeyError):
            self.manager.update_user(999, name="Test")
    
    def test_delete_user(self):
        # تست حذف کاربر
        deleted = self.manager.delete_user(self.test_user_id)
        self.assertEqual(deleted["name"], "Saba")
        
        # تست حذف کاربر ناموجود
        with self.assertRaises(KeyError):
            self.manager.delete_user(999)
    
    def test_user_count(self):
        # تست تعداد کاربران
        self.assertEqual(len(self.manager.users), 1)
        self.manager.add_user("Reza", "[email protected]")
        self.assertEqual(len(self.manager.users), 2)

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

📌 نکات کلیدی

← قبلی: لاگ‌نویسی → بعدی: محیط مجازی ↑ بازگشت به فهرست متوسط