🧪 تست نویسی با 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 و 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", "saba@example.com")
def test_add_user(self):
# تست افزودن کاربر معتبر
user_id = self.manager.add_user("Ali", "ali@example.com")
user = self.manager.get_user(user_id)
self.assertEqual(user["name"], "Ali")
self.assertEqual(user["email"], "ali@example.com")
# تست خطاهای افزودن کاربر
with self.assertRaises(ValueError):
self.manager.add_user("", "test@example.com")
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"], "saba@example.com")
# تست دریافت کاربر ناموجود
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="saba.ahmadi@example.com")
self.assertEqual(updated["name"], "Saba Ahmadi")
self.assertEqual(updated["email"], "saba.ahmadi@example.com")
# تست بهروزرسانی کاربر ناموجود
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", "reza@example.com")
self.assertEqual(len(self.manager.users), 2)
if __name__ == '__main__':
unittest.main()
📌 نکات کلیدی
- ✅ ماژول
unittestبرای تست نویسی در پایتون استفاده میشود. - ✅ کلاسهای تست باید از
unittest.TestCaseارثبری کنند. - ✅ متدهای تست باید با
test_شروع شوند. - ✅
assertEqual،assertTrue،assertFalseوassertRaisesمهمترین متدها هستند. - ✅
setUpوtearDownقبل و بعد از هر تست اجرا میشوند. - ✅
setUpClassوtearDownClassیک بار قبل و بعد از همه تستها اجرا میشوند. - ✅ با
@unittest.skipوskipIfمیتوانید تستها را اسکیپ کنید. - ✅ با
unittest.mockمیتوانید اشیاء را Mock کنید. - ✅ تست نویسی به کیفیت کد و اعتماد به برنامه کمک میکند.