Python Coding Academy
135 subscribers
7 photos
3 files
4 links
🐍 Learn Python. Build Skills. Create the Future.

📚 Python lessons from beginner to advanced
💻 Clear explanations & real-world examples
🚀 Learn to code. Learn to build.
Download Telegram
🐍 آموزش Python | پست ۲۶۰
استفاده از patch.object() در تست‌نویسی
در پست‌های قبلی با patch() آشنا شدیم. یکی از ابزارهای مرتبط با آن، patch.object() است که برای جایگزینی موقت یک ویژگی یا متد روی یک شیء یا کلاس استفاده می‌شود.
۱. جایگزینی موقت یک متد
فرض کنید کلاسی داریم که اطلاعات کاربر را از یک سرویس دریافت می‌کند:
class UserService:
def get_username(self):
return "Ali"

می‌توانیم در تست، متد get_username را موقتاً با یک مقدار ساختگی جایگزین کنیم:
import unittest
from unittest.mock import patch

class UserService:
def get_username(self):
return "Ali"

class TestUserService(unittest.TestCase):

def test_username(self):
service = UserService()

with patch.object(
service,
"get_username",
return_value="TestUser"
):
self.assertEqual(
service.get_username(),
"TestUser"
)

self.assertEqual(service.get_username(), "Ali")

در این مثال:
داخل بلوک with، متد مقدار "TestUser" را برمی‌گرداند.
پس از پایان بلوک، رفتار اصلی متد بازگردانده می‌شود.
۲. جایگزینی ویژگی یک کلاس
patch.object() فقط مخصوص متدها نیست و می‌تواند ویژگی‌های کلاس را نیز موقتاً تغییر دهد:
from unittest.mock import patch

class Config:
DEBUG = False

with patch.object(Config, "DEBUG", True):
print(Config.DEBUG)

print(Config.DEBUG)

خروجی:
True
False

۳. تفاوت patch() و patch.object()
ابزار
کاربرد
patch()
جایگزینی یک شیء با استفاده از مسیر ماژولی
patch.object()
جایگزینی ویژگی یا متد یک شیء یا کلاس مشخص
نکته مهم: هر دو ابزار پس از پایان محدوده اجرا، مقدار یا رفتار اولیه را بازمی‌گردانند؛ بنابراین برای تغییرات موقت در تست‌ها بسیار کاربردی هستند.
🎯 تمرین
کلاسی به نام Calculator بسازید که متد add() داشته باشد.
سپس با استفاده از patch.object() این متد را طوری جایگزین کنید که به‌جای جمع دو عدد، همیشه مقدار 100 را برگرداند.
بررسی کنید که پس از پایان patch.object()، متد دوباره رفتار اصلی خود را داشته باشد.
پست ۲۶۰ از دوره جامع Python 🐍
👍1
🐍 آموزش Python | پست ۲۶۱
مدیریت محدوده Patch با start() و stop()
تا اینجا از patch() و patch.object() بیشتر در قالب with استفاده کردیم. اما گاهی لازم است کنترل شروع و پایان یک Patch را به‌صورت دستی در اختیار داشته باشیم.
برای این کار از متدهای start() و stop() استفاده می‌کنیم.
۱. استفاده از start()
با فراخوانی start()، جایگزینی موقت آغاز می‌شود:
from unittest.mock import patch

class Database:
def get_user(self):
return "Ali"

db = Database()

patcher = patch.object(
db,
"get_user",
return_value="TestUser"
)

patcher.start()

print(db.get_user())

خروجی:
TestUser

۲. استفاده از stop()
برای بازگرداندن رفتار اصلی، باید stop() را فراخوانی کنیم:
patcher.stop()

print(db.get_user())

خروجی:
Ali

با اجرای stop()، جایگزینی موقت حذف می‌شود.
۳. مدیریت Patch در تست‌های unittest
اگر در چند تست به یک Patch نیاز داشته باشیم، می‌توانیم آن را در setUp() فعال کنیم و با addCleanup() مطمئن شویم که در پایان تست متوقف می‌شود:
import unittest
from unittest.mock import patch

class Database:
def get_user(self):
return "Ali"


class TestDatabase(unittest.TestCase):

def setUp(self):
self.db = Database()

patcher = patch.object(
self.db,
"get_user",
return_value="TestUser"
)

patcher.start()
self.addCleanup(patcher.stop)

def test_get_user(self):
self.assertEqual(
self.db.get_user(),
"TestUser"
)


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

چرا addCleanup() مهم است؟
چون اگر در طول تست خطایی رخ دهد، پاک‌سازی ثبت‌شده همچنان اجرا می‌شود و Patch در محیط تست باقی نمی‌ماند.
نکات مهم
هر start() باید در نهایت با stop() متوقف شود.
فراموش‌کردن stop() ممکن است روی تست‌های بعدی تأثیر بگذارد.
برای مدیریت خودکار محدوده Patch، استفاده از with معمولاً ساده‌تر و ایمن‌تر است.
در تست‌های unittest، ترکیب start() و addCleanup() برای مدیریت دستی بسیار مناسب است.
🎯 تمرین
یک کلاس به نام PaymentService بسازید که متد get_status() آن مقدار "Pending" را برگرداند.
سپس با patch.object() و استفاده از start()، مقدار آن را به "Paid" تغییر دهید و در پایان با stop() رفتار اصلی را بازگردانید.
پست ۲۶۱ از دوره جامع Python 🐍
👍1
🐍 آموزش Python | پست ۲۶۲
بررسی فراخوانی‌های Mock با assert_has_calls()
در تست‌نویسی، گاهی فقط بررسی خروجی یک تابع کافی نیست؛ باید مطمئن شویم متدهای وابسته نیز با آرگومان‌های درست و به ترتیب مورد انتظار فراخوانی شده‌اند.
برای این کار از assert_has_calls() استفاده می‌کنیم.
۱. بررسی چند فراخوانی
ابتدا یک Mock می‌سازیم و چند بار آن را با آرگومان‌های مختلف فراخوانی می‌کنیم:
from unittest.mock import Mock, call

send_message = Mock()

send_message("Hello")
send_message("Welcome")
send_message("Goodbye")

send_message.assert_has_calls([
call("Hello"),
call("Welcome"),
call("Goodbye")
])

اگر فراخوانی‌ها مطابق انتظار باشند، تست موفق می‌شود؛ در غیر این صورت AssertionError رخ می‌دهد.
۲. بررسی ترتیب فراخوانی‌ها
به‌صورت پیش‌فرض، assert_has_calls() ترتیب فراخوانی‌ها را بررسی می‌کند.
send_message.assert_has_calls([
call("Hello"),
call("Welcome")
])

این بررسی موفق است، چون این دو فراخوانی با همین ترتیب در تاریخچه Mock وجود دارند.
اما اگر ترتیب را تغییر دهیم، ممکن است تست شکست بخورد:
send_message.assert_has_calls([
call("Welcome"),
call("Hello")
])

۳. نادیده گرفتن فراخوانی‌های اضافی
پارامتر any_order=True اجازه می‌دهد ترتیب فراخوانی‌ها مهم نباشد:
send_message.assert_has_calls(
[
call("Goodbye"),
call("Hello")
],
any_order=True
)

در این حالت، وجود فراخوانی‌های موردنظر بررسی می‌شود؛ ترتیب آن‌ها اهمیتی ندارد.
نکته: به‌صورت پیش‌فرض، فراخوانی‌های مورد انتظار باید به‌صورت متوالی در تاریخچه فراخوانی‌ها وجود داشته باشند؛ اما با any_order=True ترتیب بررسی نمی‌شود.
تفاوت با ابزارهای قبلی
assert_called_once_with() یک فراخوانی با آرگومان مشخص را بررسی می‌کند.
assert_any_call() بررسی می‌کند که یک فراخوانی مشخص حداقل یک‌بار اتفاق افتاده باشد.
assert_has_calls() چند فراخوانی را به‌صورت مجموعه‌ای بررسی می‌کند.
🎯 تمرین
یک Mock به نام logger بسازید و سه پیام مختلف به آن ارسال کنید.
سپس با assert_has_calls() بررسی کنید که دو پیام اول با ترتیب صحیح فراخوانی شده‌اند.
پست ۲۶۲ از دوره جامع Python 🐍
👍1
🐍 آموزش Python | پست ۲۶۳
بررسی تعداد فراخوانی‌های Mock
در تست‌نویسی، گاهی لازم است علاوه بر بررسی خروجی، تعداد دفعات اجرای یک تابع یا متد را نیز کنترل کنیم.
کتابخانه unittest.mock چند ابزار برای این کار ارائه می‌دهد.
۱. ویژگی call_count
این ویژگی تعداد دفعات فراخوانی یک Mock را مشخص می‌کند:
from unittest.mock import Mock

send_email = Mock()

send_email()
send_email()
send_email()

print(send_email.call_count)

خروجی:
3

۲. متد assert_called_once()
بررسی می‌کند که Mock دقیقاً یک‌بار فراخوانی شده باشد:
send_email = Mock()

send_email()

send_email.assert_called_once()

اگر Mock صفر بار یا بیشتر از یک بار فراخوانی شده باشد، تست با AssertionError شکست می‌خورد.
۳. متد assert_not_called()
بررسی می‌کند که Mock اصلاً فراخوانی نشده باشد:
send_email = Mock()

send_email.assert_not_called()

اگر حتی یک‌بار فراخوانی شده باشد، این بررسی شکست می‌خورد.
۴. متد assert_called()
بررسی می‌کند که Mock حداقل یک‌بار فراخوانی شده باشد:
send_email = Mock()

send_email()

send_email.assert_called()

۵. بررسی تعداد فراخوانی در یک تست واقعی
import unittest
from unittest.mock import Mock

class TestNotification(unittest.TestCase):

def test_send_notification(self):
notify = Mock()

notify("User 1")
notify("User 2")

self.assertEqual(notify.call_count, 2)
notify.assert_called()

self.assertEqual(
notify.call_args_list[0].args,
("User 1",)
)


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

در این تست:
تعداد فراخوانی‌ها باید دقیقاً ۲ باشد.
Mock حداقل یک‌بار اجرا شده باشد.
اولین فراخوانی باید آرگومان "User 1" را دریافت کرده باشد.
نکته مهم
تفاوت این ابزارها:
ابزار
کاربرد
call_count
دریافت تعداد فراخوانی‌ها
assert_called()
حداقل یک فراخوانی
assert_called_once()
دقیقاً یک فراخوانی
assert_not_called()
بدون هیچ فراخوانی
🎯 تمرین
یک Mock به نام save_data بسازید و آن را چهار بار فراخوانی کنید.
سپس با استفاده از call_count و assert_called() تعداد و وضعیت فراخوانی‌ها را بررسی کنید.
پست ۲۶۳ از دوره جامع Python 🐍
👍1
🐍 آموزش Python | پست ۲۶۴
بازنشانی Mock با reset_mock()
در تست‌نویسی ممکن است بخواهیم یک Mock را چند بار استفاده کنیم، اما اطلاعات مربوط به فراخوانی‌های قبلی آن روی تست جدید تأثیر نگذارد.
برای این کار از متد reset_mock() استفاده می‌کنیم.
۱. پاک‌کردن تاریخچه فراخوانی‌ها
from unittest.mock import Mock

api = Mock()

api("Hello")
api("Python")

print(api.call_count)

api.reset_mock()

print(api.call_count)

خروجی:
2
0

با اجرای reset_mock() اطلاعات مربوط به فراخوانی‌های قبلی پاک می‌شوند.
۲. بررسی تاریخچه پس از Reset
from unittest.mock import Mock

api = Mock()

api(10)
api(20)

api.reset_mock()

print(api.call_args)
print(api.call_args_list)
print(api.call_count)

خروجی:
None
[]
0

۳. آیا reset_mock() مقدار return_value را هم پاک می‌کند؟
به‌صورت پیش‌فرض خیر.
from unittest.mock import Mock

api = Mock(return_value="Success")

api()
api.reset_mock()

print(api())

خروجی:
Success

اگر بخواهیم مقدار بازگشتی نیز بازنشانی شود، باید از پارامتر return_value=True استفاده کنیم:
api.reset_mock(return_value=True)

همچنین با side_effect=True می‌توان رفتار تعیین‌شده در side_effect را پاک کرد:
api.reset_mock(side_effect=True)

۴. بازنشانی Mockهای فرزند
اگر Mock دارای Mockهای فرزند باشد، reset_mock() به‌صورت پیش‌فرض تاریخچه آن‌ها را نیز بازنشانی می‌کند.
from unittest.mock import Mock

service = Mock()

service.database.save()
service.database.delete()

service.reset_mock()

print(service.mock_calls)

خروجی:
[]

نکات مهم
reset_mock() تاریخچه فراخوانی‌ها را پاک می‌کند.
به‌صورت پیش‌فرض return_value و side_effect حفظ می‌شوند.
با return_value=True مقدار بازگشتی بازنشانی می‌شود.
با side_effect=True رفتار جانبی پاک می‌شود.
این متد معمولاً برای استفاده مجدد از Mock در چند مرحله تست کاربرد دارد.
🎯 تمرین
یک Mock با return_value=100 بسازید.
آن را سه بار فراخوانی کنید، سپس با reset_mock() تاریخچه را پاک کنید و بررسی کنید:
مقدار call_count صفر شده باشد.
تاریخچه call_args_list خالی باشد.
مقدار return_value همچنان ۱۰۰ باقی مانده باشد.
پست ۲۶۴ از دوره جامع Python 🐍
👍1
🐍 آموزش Python | پست ۲۶۵
استفاده از PropertyMock برای تست Propertyها
در پایتون می‌توانیم با استفاده از @property، متدها را مانند ویژگی‌های معمولی فراخوانی کنیم.
اما هنگام تست چنین ویژگی‌هایی، گاهی لازم است مقدار آن‌ها را بدون اجرای منطق اصلی کنترل کنیم.
برای این کار از PropertyMock در کتابخانه unittest.mock استفاده می‌کنیم.
۱. یک کلاس با Property
class Product:
@property
def price(self):
return 500

استفاده از این ویژگی:
product = Product()

print(product.price)

خروجی:
500

۲. جایگزینی Property با PropertyMock
from unittest.mock import PropertyMock, patch

class Product:
@property
def price(self):
return 500


product = Product()

with patch.object(
Product,
"price",
new_callable=PropertyMock
) as mock_price:

mock_price.return_value = 1000

print(product.price)

خروجی:
1000

در این مثال، مقدار Property به‌صورت موقت تغییر کرده است؛ بدون اینکه کد اصلی آن اجرا شود.
۳. بررسی تعداد دسترسی به Property
با PropertyMock می‌توانیم بررسی کنیم که یک ویژگی چند بار خوانده شده است:
from unittest.mock import PropertyMock, patch

class User:
@property
def name(self):
return "Ali"


with patch.object(
User,
"name",
new_callable=PropertyMock
) as mock_name:

mock_name.return_value = "Test"

user = User()

print(user.name)
print(user.name)

mock_name.assert_called_with()
print(mock_name.call_count)

خروجی:
Test
Test
2

نکته مهم
PropertyMock باید روی کلاس یا شیءای قرار بگیرد که Property از آن تعریف شده است. در مثال بالا، آن را روی کلاس User قرار دادیم.
همچنین برای تغییر مقدار Property از mock_name.return_value و برای بررسی مقداردهی آن می‌توان از mock_name.assert_called_once_with(value) استفاده کرد.
🎯 تمرین
کلاسی به نام Account بسازید که یک Property به نام balance داشته باشد و مقدار اصلی آن 2000 باشد.
سپس با PropertyMock مقدار آن را در تست به 5000 تغییر دهید و بررسی کنید که مقدار اصلی پس از پایان Patch دوباره قابل‌دسترسی باشد.
پست ۲۶۵ از دوره جامع Python 🐍
👍1
🇮🇷 ۱۳ آبان | روز دانش‌آموز و روز مبارزه با استکبار جهانی

۱۳ آبان، یادآور روزهایی است که دانش‌آموزان و دانشجویان ایرانی برای استقلال، آزادی و آینده کشورشان ایستادگی کردند.

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

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

بیایید با یادگیری، خلاقیت و پیشرفت علمی، سهمی در ساختن آینده داشته باشیم.

🌱 آینده را کسانی می‌سازند که امروز برای یادگرفتن تلاش می‌کنند.

🇮🇷 روز دانش‌آموز گرامی باد.
👍1👎1