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 — پست ۲۴۵
تفاوت setUp() و tearDown() در تست‌نویسی
وقتی تعداد تست‌ها زیاد می‌شود، ممکن است چند تست به داده‌ها یا منابع اولیه یکسانی نیاز داشته باشند. در کتابخانه unittest می‌توانیم از setUp() و tearDown() برای مدیریت آماده‌سازی و پاک‌سازی هر تست استفاده کنیم.
۱. متد setUp()
این متد پیش از اجرای هر متد تست اجرا می‌شود و برای آماده‌سازی شرایط تست کاربرد دارد.
۲. متد tearDown()
این متد پس از اجرای هر متد تست، در صورت موفقیت یا شکست آن، اجرا می‌شود و برای پاک‌سازی منابع کاربرد دارد؛ البته اگر اجرای setUp() با خطا مواجه شود، tearDown() اجرا نمی‌شود.
🔹 مثال عملی
import unittest


class TestCalculator(unittest.TestCase):

def setUp(self):
self.numbers = [10, 20, 30]
print("Preparing test")

def tearDown(self):
print("Cleaning up test")

def test_sum(self):
self.assertEqual(sum(self.numbers), 60)

def test_length(self):
self.assertEqual(len(self.numbers), 3)


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

🔍 ترتیب اجرا
برای هر تست، ترتیب کلی به این شکل است:
setUp()
↓
اجرای متد تست
↓
tearDown()

بنابراین، برای دو متد تست بالا، setUp() و tearDown() هرکدام دو بار اجرا می‌شوند.
نکته: ترتیب اجرای متدهای تست را نباید بدون دلیل مشخص، ترتیب نوشتن آن‌ها در کلاس فرض کنید.
🔹 چرا این روش مفید است؟
فرض کنید چند تست به یک فهرست اولیه نیاز دارند. به‌جای تکرار ساخت فهرست در هر تست، آن را در setUp() تعریف می‌کنیم.
همچنین هر تست، نمونه جداگانه‌ای از کلاس تست دریافت می‌کند؛ بنابراین تغییر self.numbers در یک تست، به‌طور معمول روی تست دیگر تأثیر نمی‌گذارد.
🔸 تفاوت با setUpClass()
اگر بخواهیم آماده‌سازی فقط یک بار برای کل کلاس تست انجام شود، می‌توانیم از setUpClass() استفاده کنیم. این متد باید با @classmethod تعریف شود.
@classmethod
def setUpClass(cls):
cls.shared_data = [1, 2, 3]

این روش برای منابع پرهزینه‌ای مناسب است که قرار است بین تست‌های یک کلاس مشترک باشند؛ البته باید مراقب تغییر داده‌های مشترک باشیم.
🎯 تمرین
کلاسی به نام TestShoppingCart بساز که:
۱. در setUp() یک سبد خرید با دو محصول ایجاد کند.
۲. در یک تست، تعداد محصولات را بررسی کند.
۳. در تست دیگر، مجموع قیمت محصولات را بررسی کند.
۴. در tearDown() یک پیام برای پایان هر تست چاپ کند.
پست ۲۴۵ از دوره جامع Python 🐍
1👍1
📢 اگر اینترنت بین‌الملل قطع بشه (نت ملی)، ترجیح می‌دید کانال ما در کدوم پیام‌رسان ایرانی فعالیتش رو ادامه بده؟ 🤔
Anonymous Poll
30%
🟠 ایتا
10%
🟣 روبیکا
70%
🟢 بله
0%
🔵 شاد
👍1
چالش برنامه نویسی بدون جایزه! 🔥

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

مثلا برنامتون باید بین این 2 تا کد تفاوت قائل بشه:
#1
print("Hello Dev FA")


#2
while True:
    pass

اما داستان به همینجا ختم نمی‌شه
کدتون باید برای هر برنامه و ورودی دلخواه جواب درست بده و خودشم همیشه تو زمان محدود به جواب برسه
13👍1
🐍 آموزش جامع Python — پست ۲۴۶
مدیریت منابع تست با addCleanup()
در پست قبل با setUp() و tearDown() آشنا شدیم. حالا می‌خواهیم روش دیگری در unittest یاد بگیریم: متد addCleanup().
این متد برای ثبت عملیات پاک‌سازی استفاده می‌شود؛ عملیاتی که پس از پایان تست باید اجرا شوند، مانند بستن فایل یا آزادسازی یک منبع.
🔹 مثال عملی
فرض کنید تست ما یک فایل موقت باز می‌کند. می‌خواهیم مطمئن شویم فایل پس از پایان تست بسته می‌شود.
import unittest


class TestFileHandling(unittest.TestCase):

def setUp(self):
self.file = open("test.txt", "w", encoding="utf-8")

self.addCleanup(self.file.close)

def test_file_is_open(self):
self.assertFalse(self.file.closed)

def test_write_to_file(self):
self.file.write("Hello Python")


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

🔍 کد چگونه کار می‌کند؟
open() فایل را باز می‌کند.
self.addCleanup(self.file.close) تابع بستن فایل را برای مرحله پاک‌سازی ثبت می‌کند.
پس از پایان هر تست، تابع ثبت‌شده اجرا می‌شود؛ حتی اگر تست با شکست مواجه شود یا اجرای آن با خطا متوقف شود.
از آنجا که setUp() برای هر تست اجرا می‌شود، هر تست فایل خودش را باز می‌کند و عملیات پاک‌سازی مربوط به همان فایل را ثبت می‌کند.
🔸 تفاوت addCleanup() با tearDown()
ویژگی
tearDown()
addCleanup()
روش استفاده
بازنویسی یک متد
ثبت یک یا چند تابع
تعداد عملیات پاک‌سازی
می‌توان چند عملیات را در آن نوشت
می‌توان چند تابع جداگانه ثبت کرد
ترتیب اجرا
پس از تست، در صورت موفقیت setUp()
توابع ثبت‌شده با ترتیب معکوس ثبت اجرا می‌شوند
خطا در setUp()
ممکن است اجرا نشود
توابعی که پیش از خطا ثبت شده‌اند، اجرا می‌شوند
نکته مهم: توابع addCleanup() حتی اگر متد تست شکست بخورد یا tearDown() با خطا مواجه شود، اجرا می‌شوند. اگر setUp() قبل از ثبت عملیات پاک‌سازی با خطا مواجه شود، طبیعتاً پاک‌سازی ثبت‌نشده‌ای وجود ندارد.
🎯 تمرین
کلاسی برای تست یک منبع فرضی بساز که:
۱. در setUp() یک فهرست ایجاد کند.
۲. با addCleanup() تابعی را برای پاک‌سازی ثبت کند.
۳. در تابع پاک‌سازی، یک پیام چاپ کند.
۴. بررسی کند که داده‌ها در زمان اجرای تست در دسترس هستند.
هدف این است که تفاوت آماده‌سازی تست و پاک‌سازی خودکار منابع را در عمل درک کنید.
پست ۲۴۶ از دوره جامع Python 🐍
👍1
🐍 آموزش جامع Python — پست ۲۴۷
مدیریت منابع در تست‌ها با setUpClass() و tearDownClass()
در تست‌نویسی، بعضی منابع برای هر تست به‌صورت جداگانه آماده می‌شوند؛ اما بعضی دیگر، مانند یک منبع مشترک یا داده‌های پرهزینه برای آماده‌سازی، بهتر است فقط یک بار برای کل کلاس ایجاد شوند.
در unittest دو متد ویژه برای این کار داریم:
setUpClass() برای آماده‌سازی مشترک پیش از اجرای تست‌های کلاس
tearDownClass() برای پاک‌سازی مشترک پس از پایان اجرای تست‌های کلاس
۱. ساختار اصلی
import unittest


class TestExample(unittest.TestCase):

@classmethod
def setUpClass(cls):
print("Class setup")

@classmethod
def tearDownClass(cls):
print("Class cleanup")

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

def test_second(self):
self.assertTrue(10 > 3)


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

در این مثال، setUpClass() یک بار پیش از اجرای تست‌های این کلاس اجرا می‌شود و tearDownClass() پس از پایان اجرای تست‌های کلاس اجرا خواهد شد.
۲. چرا از @classmethod استفاده می‌کنیم؟
این دو متد به‌جای یک نمونه از کلاس تست، خود کلاس را دریافت می‌کنند؛ بنابراین پارامتر آن‌ها cls است.
@classmethod
def setUpClass(cls):
cls.shared_data = [10, 20, 30]

اکنون داده از طریق کلاس قابل دسترسی است:
self.assertEqual(sum(self.shared_data), 60)

۳. تفاوت با setUp() و tearDown()
متد
زمان اجرا
setUp()
پیش از هر تست
tearDown()
پس از هر تست، اگر setUp() با موفقیت تمام شده باشد
setUpClass()
یک بار پیش از تست‌های کلاس
tearDownClass()
پس از پایان اجرای تست‌های کلاس
برای مثال، اگر کلاس سه متد تست داشته باشد، در اجرای معمول هر چهار متد مربوط به چرخه عمر به این صورت رفتار می‌کنند:
setUpClass() یک بار اجرا می‌شود.
setUp() سه بار اجرا می‌شود.
tearDown() سه بار اجرا می‌شود.
tearDownClass() یک بار اجرا می‌شود.
البته اگر راه‌اندازی با خطا مواجه شود یا اجرای تست‌ها به شکل غیرمعمول متوقف شود، این ترتیب ممکن است کامل نشود.
۴. مثال کاربردی با داده‌های مشترک
import unittest


class TestProducts(unittest.TestCase):

@classmethod
def setUpClass(cls):
cls.products = {
"book": 150,
"pen": 20,
"bag": 500,
}

def test_book_price(self):
self.assertEqual(self.products["book"], 150)

def test_pen_price(self):
self.assertEqual(self.products["pen"], 20)

def test_product_count(self):
self.assertEqual(len(self.products), 3)


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

در اینجا دیکشنری یک بار ساخته می‌شود و همه تست‌ها آن را می‌خوانند.
نکته مهم: داده‌های مشترک ممکن است بین تست‌ها تغییر کنند. اگر یک تست دیکشنری را تغییر دهد، تست‌های دیگر نیز می‌توانند تغییر را مشاهده کنند. بنابراین، برای داده‌های قابل‌تغییر یا تست‌های مستقل، معمولاً ساخت داده در setUp() انتخاب امن‌تری است.
همچنین tearDownClass() فقط زمانی اجرا می‌شود که راه‌اندازی کلاس با موفقیت انجام شده باشد. برای منابعی که باید حتی در صورت شکست بخشی از setUpClass() آزاد شوند، ثبت پاک‌سازی با cls.addClassCleanup() می‌تواند مناسب‌تر باشد.
🎯 تمرین
کلاسی به نام TestInventory بساز که:
۱. در setUpClass() یک دیکشنری مشترک از سه محصول ایجاد کند.
۲. در تست اول، قیمت یکی از محصولات را بررسی کند.
۳. در تست دوم، تعداد محصولات را بررسی کند.
۴. در tearDownClass() پیام پایان تست‌های کلاس را چاپ کند.
۵. توضیح بده چرا نباید بدون دلیل، داده‌های مشترک را در هر تست تغییر دهیم.
اجرای تست‌ها:
python -m unittest -v

در پست بعدی به سراغ مدیریت خطا هنگام آماده‌سازی منابع تست می‌رویم تا تست‌ها حتی در شرایط شکست نیز پاک‌سازی قابل‌اعتمادتری داشته باشند.
پست ۲۴۷ از دوره جامع Python 🐍
1👍1
🐍 آموزش جامع Python — پست ۲۴۸
پاک‌سازی مطمئن منابع با addClassCleanup()
در پست قبل با setUpClass() و tearDownClass() آشنا شدیم. حالا یک مسئله مهم را بررسی می‌کنیم: اگر آماده‌سازی منابع با خطا مواجه شود، چگونه مطمئن شویم منابعی که قبلاً ایجاد شده‌اند آزاد می‌شوند؟
برای این کار، unittest متدی به نام addClassCleanup() در اختیارمان قرار می‌دهد.
۱. مشکل tearDownClass()
فرض کنید در مرحله آماده‌سازی کلاس، یک فایل باز می‌کنیم و سپس هنگام آماده‌سازی منبع دیگری خطایی رخ می‌دهد.
import unittest


class TestResources(unittest.TestCase):

@classmethod
def setUpClass(cls):
cls.file = open(
"test.txt",
"w",
encoding="utf-8"
)

raise RuntimeError("Setup failed")

@classmethod
def tearDownClass(cls):
cls.file.close()

در این مثال، اگر setUpClass() با خطا متوقف شود، نباید به اجرای tearDownClass() برای بستن فایل تکیه کنیم. در نتیجه، فایل ممکن است باز بماند.
۲. راه‌حل با addClassCleanup()
بهتر است عملیات پاک‌سازی را بلافاصله پس از ایجاد منبع ثبت کنیم:
import unittest


class TestResources(unittest.TestCase):

@classmethod
def setUpClass(cls):
cls.file = open(
"test.txt",
"w",
encoding="utf-8"
)

cls.addClassCleanup(cls.file.close)

# ادامه آماده‌سازی منابع
# اگر این بخش خطا بدهد، پاک‌سازی ثبت‌شده اجرا می‌شود.

def test_file_is_open(self):
self.assertFalse(self.file.closed)


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

🔍 چه اتفاقی می‌افتد؟
۱. فایل باز می‌شود.
۲. تابع close با addClassCleanup() ثبت می‌شود.
۳. اگر ادامه آماده‌سازی کلاس با خطا مواجه شود، عملیات پاک‌سازی ثبت‌شده همچنان اجرا می‌شود.
۴. اگر تست‌ها اجرا شوند، پاک‌سازی کلاس پس از پایان اجرای تست‌های آن انجام می‌شود.
۳. ترتیب اجرای پاک‌سازی‌ها
اگر چند عملیات ثبت کنیم:
@classmethod
def setUpClass(cls):
cls.addClassCleanup(print, "Cleanup A")
cls.addClassCleanup(print, "Cleanup B")

عملیات پاک‌سازی به ترتیب معکوس ثبت اجرا می‌شوند؛ بنابراین ابتدا Cleanup B و سپس Cleanup A چاپ می‌شود.
این ترتیب برای منابع وابسته به یکدیگر اهمیت دارد. معمولاً باید منبعی که آخر از همه ایجاد شده، زودتر آزاد شود.
۴. تفاوت سه روش
روش
کاربرد
tearDown()
پاک‌سازی پس از هر تست
tearDownClass()
پاک‌سازی مشترک پس از اجرای تست‌های کلاس
addClassCleanup()
ثبت عملیات پاک‌سازی کلاس، حتی اگر setUpClass() شکست بخورد
نکته: addClassCleanup() در نسخه‌های جدید پایتون در دسترس است؛ برای سازگاری با نسخه‌های بسیار قدیمی، نسخه پایتون را بررسی کنید.
🎯 تمرین
کلاسی طراحی کن که در setUpClass() یک فایل باز کند و بلافاصله تابع بستن آن را با addClassCleanup() ثبت کند.
سپس:
یک تست برای بررسی باز بودن فایل بنویس.
توضیح بده چرا ثبت پاک‌سازی باید بلافاصله پس از باز کردن فایل انجام شود.
مشخص کن اگر آماده‌سازی بعد از باز شدن فایل با خطا مواجه شود، چه اتفاقی می‌افتد.
پست ۲۴۸ از دوره جامع Python 🐍
1👍1
🐍 آموزش جامع Python — پست ۲۴۹
تست‌کردن خروجی چاپ‌شده با unittest.mock.patch
گاهی تابعی مقدار مشخصی برنمی‌گرداند، بلکه نتیجه را با print() نمایش می‌دهد. در چنین شرایطی، چگونه خروجی آن را آزمایش کنیم؟
در پایتون می‌توانیم با unittest.mock.patch، جریان خروجی استاندارد یعنی sys.stdout را موقتاً جایگزین کنیم و متن چاپ‌شده را بررسی کنیم.
۱. تابعی برای نمایش اطلاعات
فرض کنید فایل reports.py شامل این تابع است:
def show_total(total):
print(f"Total: {total}")

اجرای زیر:
show_total(250)

این متن را چاپ می‌کند:
Total: 250

اما برای تست خودکار، بهتر است به‌جای بررسی دستی ترمینال، خروجی را در تست دریافت کنیم.
۲. استفاده از patch() و StringIO
import unittest
from unittest.mock import patch
from io import StringIO

from reports import show_total


class TestReports(unittest.TestCase):

@patch("sys.stdout", new_callable=StringIO)
def test_show_total(self, mock_stdout):
show_total(250)

self.assertEqual(
mock_stdout.getvalue(),
"Total: 250\n"
)


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

۳. بررسی بخش‌های مهم کد
StringIO چیست؟
StringIO یک جریان متنی در حافظه است که می‌توان روی آن نوشت و محتوایش را خواند؛ بدون اینکه فایل واقعی روی دیسک ایجاد شود.
patch("sys.stdout", new_callable=StringIO) چه می‌کند؟
در طول تست، sys.stdout را با یک نمونه جدید از StringIO جایگزین می‌کند. بنابراین، فراخوانی print() متن را در این جریان می‌نویسد.
getvalue() چه کاری انجام می‌دهد؟
تمام متن ذخیره‌شده در جریان را به‌صورت رشته برمی‌گرداند.
۴. چرا در انتهای خروجی \n داریم؟
تابع print() به‌طور پیش‌فرض در پایان متن یک کاراکتر خط جدید اضافه می‌کند.
بنابراین:
print("Python")

معمولاً خروجی ثبت‌شده زیر را دارد:
"Python\n"

اگر مقدار end را تغییر دهیم، نتیجه هم تغییر می‌کند:
print("Python", end="!")

در این حالت، خروجی دقیق برابر "Python!" خواهد بود.
۵. بررسی خروجی چندخطی
تابع زیر را در نظر بگیر:
def show_user(name, age):
print(f"Name: {name}")
print(f"Age: {age}")

تست آن می‌تواند به این شکل باشد:
@patch("sys.stdout", new_callable=StringIO)
def test_show_user(self, mock_stdout):
show_user("Sara", 25)

self.assertEqual(
mock_stdout.getvalue(),
"Name: Sara\nAge: 25\n"
)

توجه کنید که این متد تست باید داخل یک کلاس فرزند unittest.TestCase قرار بگیرد.
⚠️ نکته مهم
جایگزینی sys.stdout بر خروجی‌هایی اثر می‌گذارد که از همان جریان استفاده می‌کنند. اگر کد مستقیماً از جریان دیگری، مانند sys.stderr، استفاده کند، باید آن جریان را جداگانه بررسی کنید.
همچنین برای تست خروجی چاپ‌شده، معمولاً بهتر است متن مورد انتظار را دقیق بررسی کنید؛ فاصله‌ها و خط‌های جدید هم بخشی از خروجی هستند.
🎯 تمرین
تابعی به نام display_product(name, price) بنویس که این دو خط را چاپ کند:
Product: Keyboard
Price: 1200

سپس با patch() و StringIO تستی بنویس که خروجی کامل را بررسی کند.
پست ۲۴۹ از دوره جامع Python 🐍
1👍1
🐍 آموزش جامع Python — پست ۲۵۰
تست‌کردن فایل‌ها و پوشه‌ها با tempfile
در پروژه‌های واقعی، توابع زیادی با فایل‌ها و پوشه‌ها کار می‌کنند؛ مثلاً خواندن تنظیمات، ذخیره گزارش یا پردازش فایل‌های ورودی.
اما اگر تست‌ها مستقیماً فایل‌های واقعی پروژه را تغییر دهند، ممکن است اطلاعات مهمی از بین برود یا نتیجه تست‌ها به وضعیت سیستم وابسته شود.
ماژول استاندارد tempfile امکان ایجاد فایل‌ها و پوشه‌های موقت را فراهم می‌کند تا تست‌ها محیط مستقل‌تری داشته باشند.
۱. ایجاد فایل موقت با TemporaryFile
import tempfile

with tempfile.TemporaryFile(
mode="w+t",
encoding="utf-8"
) as file:
file.write("Hello Python")
file.seek(0)

content = file.read()
print(content)

خروجی:
Hello Python

در این مثال:
w+t فایل را برای خواندن و نوشتن متن باز می‌کند.
write() متن را می‌نویسد.
seek(0) موقعیت خواندن را به ابتدای فایل برمی‌گرداند.
read() محتوای فایل را می‌خواند.
با پایان بلوک with، فایل بسته می‌شود و فایل موقت معمولاً به‌صورت خودکار حذف می‌شود.
رفتار دقیق حذف فایل موقت به نوع تابع و سیستم‌عامل وابسته است.
۲. تست یک تابع که از مسیر فایل استفاده می‌کند
فرض کنید تابع زیر در فایل config_reader.py قرار دارد:
from pathlib import Path


def read_config(path):
return Path(path).read_text(encoding="utf-8")

می‌خواهیم بررسی کنیم که تابع محتوای فایل را درست می‌خواند؛ بدون اینکه به یک فایل تنظیمات واقعی وابسته باشیم.
نوشتن تست
import unittest
import tempfile
from pathlib import Path

from config_reader import read_config


class TestConfigReader(unittest.TestCase):

def test_read_config(self):
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "config.txt"
path.write_text(
"theme=dark",
encoding="utf-8"
)

result = read_config(path)

self.assertEqual(result, "theme=dark")


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

🔍 چرا این روش مناسب است؟
۱. TemporaryDirectory() یک پوشه موقت ایجاد می‌کند.
۲. فایل config.txt داخل همان پوشه ساخته می‌شود.
۳. تابع واقعی read_config() روی مسیر فایل اجرا می‌شود.
۴. با پایان بلوک with، پوشه موقت و محتوای آن برای پاک‌سازی علامت‌گذاری و حذف می‌شوند.
در این تست از patch() استفاده نکردیم؛ چون هدف ما بررسی رفتار واقعی خواندن فایل است، نه شبیه‌سازی آن.
۳. تفاوت ابزارهای مهم tempfile
ابزار
کاربرد
TemporaryFile()
ایجاد فایل موقت برای کار مستقیم با محتوای آن
NamedTemporaryFile()
ایجاد فایل موقت با نام و مسیر قابل‌دسترسی
TemporaryDirectory()
ایجاد پوشه موقت برای نگهداری یک یا چند فایل
SpooledTemporaryFile()
نگهداری داده در حافظه تا رسیدن به آستانه مشخص و سپس انتقال به فایل موقت
نکته: اگر تابع مورد آزمایش به مسیر واقعی فایل نیاز دارد، TemporaryDirectory() یا NamedTemporaryFile() معمولاً انتخاب مناسب‌تری از TemporaryFile() است؛ زیرا TemporaryFile() الزاماً مسیر قابل‌استفاده‌ای در اختیار برنامه قرار نمی‌دهد.
۴. یک اشتباه رایج
اگر تابع شما مسیر نسبی می‌گیرد، نباید فرض کنید فایل همیشه نسبت به پوشه پروژه پیدا می‌شود. مسیر نسبی معمولاً نسبت به پوشه کاری فعلی فرایند تفسیر می‌شود.
استفاده از مسیر صریح ساخته‌شده با Path در تست، وابستگی به پوشه کاری را کاهش می‌دهد.
🎯 تمرین
تابعی به نام count_lines(path) بنویس که تعداد خطوط یک فایل متنی را برگرداند.
سپس با TemporaryDirectory() فایلی بساز که شامل سه خط باشد و تستی بنویس که نتیجه تابع برابر 3 باشد.
پست ۲۵۰ از دوره جامع Python 🐍
1👍1
🐍 آموزش جامع Python — پست ۲۵۱
تست‌کردن کدهای ناهمگام با unittest.IsolatedAsyncioTestCase
در پایتون، توابعی که با async def تعریف می‌شوند، توابع ناهمگام (Asynchronous) هستند. برای تست این توابع باید اجرای آن‌ها را با await مدیریت کنیم.
کتابخانه استاندارد unittest کلاسی به نام IsolatedAsyncioTestCase دارد که برای تست کدهای ناهمگام طراحی شده است.
🔹 ۱. یک تابع ناهمگام بسازیم
import asyncio


async def get_user_name():
await asyncio.sleep(0.1)
return "Sara"

فراخوانی این تابع یک شیء coroutine ایجاد می‌کند. برای دریافت نتیجه، باید آن را اجرا و منتظر تکمیلش بمانیم.
🔹 ۲. نوشتن تست ناهمگام
import unittest
import asyncio


async def get_user_name():
await asyncio.sleep(0.1)
return "Sara"


class TestUser(unittest.IsolatedAsyncioTestCase):

async def test_get_user_name(self):
result = await get_user_name()

self.assertEqual(result, "Sara")


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

🔍 نکات مهم
متد تست ناهمگام باید با async def تعریف شود.
درون متد تست، برای انتظار نتیجه تابع ناهمگام از await استفاده می‌کنیم.
IsolatedAsyncioTestCase محیط اجرای ناهمگام مستقلی برای تست‌ها فراهم می‌کند.
در این مثال، asyncio.sleep() صرفاً برای شبیه‌سازی یک عملیات زمان‌بر است.
🔹 ۳. بررسی خطا در تابع ناهمگام
می‌توانیم خطاهای مورد انتظار را نیز آزمایش کنیم:
async def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")

return a / b

تست:
async def test_divide_by_zero(self):
with self.assertRaises(ValueError):
await divide(10, 0)

این متد تست باید داخل کلاس TestUser یا کلاس دیگری از نوع IsolatedAsyncioTestCase قرار بگیرد.
نکته: اگر از assertRaises() استفاده می‌کنیم، باید فراخوانی coroutine را با await داخل بلوک آن قرار دهیم تا خطای زمان اجرای تابع واقعاً بررسی شود.
🎯 تمرین
تابع ناهمگام زیر را پیاده‌سازی و آزمایش کن:
async def get_square(number)
این تابع باید مربع عدد را برگرداند. سپس دو تست بنویس:
۱. بررسی کن که get_square(5) نتیجه 25 را برمی‌گرداند.
۲. اگر تصمیم گرفتی ورودی منفی مجاز نباشد، برای آن ValueError تعریف کن و تست مناسبی بنویس.
اجرای تست‌ها:
python -m unittest -v

پست ۲۵۱ از دوره جامع Python 🐍
👍1
🐍 آموزش جامع Python — پست ۲۵۲
شبیه‌سازی توابع ناهمگام با AsyncMock
در پست قبل یاد گرفتیم توابع async را با IsolatedAsyncioTestCase آزمایش کنیم. حالا می‌خواهیم وابستگی‌های ناهمگام را بدون اجرای عملیات واقعی شبیه‌سازی کنیم.
ابزار AsyncMock از ماژول unittest.mock برای همین کار طراحی شده است.
🔹 ۱. چرا به AsyncMock نیاز داریم؟
فرض کنید تابعی برای دریافت اطلاعات کاربر از یک API داریم. هنگام تست نمی‌خواهیم درخواست واقعی اینترنتی ارسال کنیم؛ فقط می‌خواهیم رفتار تابع خودمان را بررسی کنیم.
فایل user_service.py:
async def fetch_user(api):
return await api.get_user(10)

این تابع به یک متد ناهمگام به نام get_user() وابسته است.
🔹 ۲. نوشتن تست با AsyncMock
import unittest
from unittest.mock import AsyncMock


async def fetch_user(api):
return await api.get_user(10)


class TestUserService(unittest.IsolatedAsyncioTestCase):

async def test_fetch_user(self):
api = unittest.mock.Mock()
api.get_user = AsyncMock(
return_value={"id": 10, "name": "Sara"}
)

result = await fetch_user(api)

self.assertEqual(
result,
{"id": 10, "name": "Sara"}
)

api.get_user.assert_awaited_once_with(10)


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

🔍 توضیح کد
AsyncMock یک وابستگی ناهمگام ساختگی ایجاد می‌کند.
return_value مشخص می‌کند که انتظار داریم نتیجه await api.get_user(10) چه باشد.
assert_awaited_once_with(10) بررسی می‌کند که متد دقیقاً یک بار با آرگومان 10 مورد await قرار گرفته باشد.
در این تست، هیچ درخواست واقعی به API ارسال نمی‌شود.
🔸 تفاوت assert_called_once_with() و assert_awaited_once_with()
متد
چه چیزی را بررسی می‌کند؟
assert_called_once_with()
تابع یک بار با آرگومان‌های مشخص فراخوانی شده است
assert_awaited_once_with()
فراخوانی ناهمگام یک بار با آرگومان‌های مشخص مورد await قرار گرفته است
برای بررسی رفتار ناهمگام، معمولاً assert_awaited_once_with() دقیق‌تر است.
⚠️ نکته مهم
در کد بالا از unittest.mock.Mock() استفاده کرده‌ایم؛ بنابراین باید import unittest نیز وجود داشته باشد. اگر فقط AsyncMock را وارد کرده باشید، می‌توانید به‌جای آن مستقیماً بنویسید:
from unittest.mock import AsyncMock, Mock

api = Mock()
api.get_user = AsyncMock(
return_value={"id": 10, "name": "Sara"}
)

🎯 تمرین
تابعی به نام fetch_product(api) بنویس که محصول شماره 5 را با await api.get_product(5) دریافت کند.
سپس با AsyncMock تستی بنویس که:
۱. نتیجه ساختگی {"id": 5, "name": "Keyboard"} باشد.
۲. نتیجه تابع را بررسی کند.
۳. تأیید کند get_product دقیقاً یک بار با آرگومان 5 مورد await قرار گرفته است.
پست ۲۵۲ از دوره جامع Python 🐍
👍1
🐍 آموزش جامع Python — پست ۲۵۳
تست‌کردن فراخوانی‌ها با call_args و call_args_list
در تست‌نویسی، گاهی فقط دانستن نتیجه یک تابع کافی نیست؛ باید بررسی کنیم تابع‌های وابسته با چه آرگومان‌هایی و چند بار فراخوانی شده‌اند.
در unittest.mock ابزارهایی مانند call_args و call_args_list این امکان را فراهم می‌کنند.
🔹 ۱. بررسی آخرین فراخوانی با call_args
from unittest.mock import Mock

send_message = Mock()

send_message("Sara", "Hello")
send_message("Ali", "Welcome")

print(send_message.call_args)

خروجی:
call('Ali', 'Welcome')

ویژگی call_args اطلاعات آخرین فراخوانی را نگه می‌دارد؛ نه تمام فراخوانی‌ها را.
🔹 ۲. بررسی تمام فراخوانی‌ها با call_args_list
from unittest.mock import Mock

send_message = Mock()

send_message("Sara", "Hello")
send_message("Ali", "Welcome")

print(send_message.call_args_list)

خروجی:
[call('Sara', 'Hello'), call('Ali', 'Welcome')]

این ویژگی تمام فراخوانی‌ها را به ترتیب وقوع نگه می‌دارد.
🔹 ۳. بررسی آرگومان‌ها
هر فراخوانی ثبت‌شده شامل دو بخش است:
args: آرگومان‌های موقعیتی (Positional Arguments)
kwargs: آرگومان‌های نام‌دار (Keyword Arguments)
مثال:
from unittest.mock import Mock

save_user = Mock()

save_user(10, name="Sara", active=True)

args, kwargs = save_user.call_args

print(args)
print(kwargs)

خروجی:
(10,)
{'name': 'Sara', 'active': True}

وجود ویرگول در (10,) نشان می‌دهد که این یک تاپل تک‌عضوی است.
🔹 ۴. استفاده در تست واقعی
فرض کنیم تابع زیر را داریم:
def notify_users(send_message, users):
for user in users:
send_message(user, "New update")

تست آن:
import unittest
from unittest.mock import Mock, call


class TestNotifications(unittest.TestCase):

def test_notify_users(self):
send_message = Mock()

notify_users(
send_message,
["Sara", "Ali"]
)

self.assertEqual(
send_message.call_args_list,
[
call("Sara", "New update"),
call("Ali", "New update"),
],
)


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

در این تست، علاوه بر تعداد فراخوانی‌ها، ترتیب و آرگومان‌های هر فراخوانی نیز بررسی می‌شود.
⚠️ نکته مهم
اگر فقط می‌خواهید تعداد فراخوانی‌ها را بررسی کنید، از call_count استفاده کنید. اگر آخرین آرگومان‌ها مهم‌اند، call_args مناسب است؛ و اگر تمام فراخوانی‌ها اهمیت دارند، call_args_list انتخاب بهتری است.
همچنین assert_has_calls() برای بررسی وجود یک توالی از فراخوانی‌ها کاربرد دارد؛ به‌طور پیش‌فرض فراخوانی‌های اضافی قبل یا بعد از آن توالی مجازند، اما ترتیب فراخوانی‌های مورد انتظار باید حفظ شود.
🎯 تمرین
تابعی به نام log_actions(logger, actions) بنویس که برای هر عضو فهرست، تابع logger را با آن عضو فراخوانی کند.
سپس با Mock بررسی کن:
۱. تعداد فراخوانی‌ها برابر تعداد اعضای فهرست باشد.
۲. آرگومان‌های هر فراخوانی درست باشند.
۳. ترتیب فراخوانی‌ها مطابق ترتیب فهرست ورودی باشد.
پست ۲۵۳ از دوره جامع Python 🐍
👍1
🐍 آموزش جامع Python — پست ۲۵۴
تست‌کردن فراخوانی‌های زنجیره‌ای با mock_calls
در پست قبل با call_args و call_args_list آشنا شدیم. حالا می‌خواهیم حالتی را بررسی کنیم که یک شیء ساختگی، متدهای تو‌در‌تو یا زنجیره‌ای دارد.
برای بررسی تمام این فراخوانی‌ها می‌توانیم از ویژگی mock_calls استفاده کنیم.
🔹 ۱. یک مثال ساده
from unittest.mock import Mock

client = Mock()

client.session.get("/users")
client.session.close()

print(client.mock_calls)

خروجی:
[call.session.get('/users'), call.session.close()]

ویژگی mock_calls فراخوانی‌های ثبت‌شده روی خود Mock و متدها و ویژگی‌های ساختگی وابسته به آن را به ترتیب وقوع نگه می‌دارد.
🔹 ۲. تفاوت call_args_list و mock_calls
from unittest.mock import Mock

client = Mock()

client.get("/users")
client.session.close()

print(client.call_args_list)
print(client.mock_calls)

خروجی:
[call('/users')]
[call.get('/users'), call.session.close()]

در این مثال:
call_args_list فقط فراخوانی‌های مستقیم خود client را ثبت می‌کند.
mock_calls فراخوانی‌های متدها و اشیای ساختگی وابسته را نیز ثبت می‌کند.
🔹 ۳. استفاده در تست
فرض کنیم تابعی داریم که اطلاعات را دریافت و سپس اتصال را می‌بندد:
def load_users(client):
client.session.get("/users")
client.session.close()

تست:
import unittest
from unittest.mock import Mock, call


class TestUserClient(unittest.TestCase):

def test_load_users(self):
client = Mock()

load_users(client)

self.assertEqual(
client.mock_calls,
[
call.session.get("/users"),
call.session.close(),
],
)


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

این تست بررسی می‌کند که هر دو فراخوانی با آرگومان‌های درست و به ترتیب مورد انتظار اتفاق افتاده‌اند.
⚠️ نکته مهم
mock_calls ترتیب فراخوانی‌ها را نشان می‌دهد؛ اما همیشه برای اثبات ترتیب اجرای تمام عملیات داخلی کافی نیست. به‌خصوص، آرگومان‌های یک فراخوانی والد در اطلاعات فراخوانی‌های تو‌در‌توی آن تکرار نمی‌شوند. اگر بررسی دقیق آرگومان‌های والد ضروری است، آن‌ها را جداگانه آزمایش کنید.
همچنین استفاده از Mock نباید جای تست رفتار واقعی سیستم را بگیرد؛ Mock برای بررسی تعامل با وابستگی‌ها مناسب است.
🎯 تمرین
تابعی به نام process_order(api) بنویس که به‌ترتیب:
۱. api.orders.create() را با مقدار "ORD-101" فراخوانی کند.
۲. api.orders.confirm() را فراخوانی کند.
سپس با Mock و mock_calls بررسی کن که این دو عملیات به ترتیب درست انجام شده‌اند.
پست ۲۵۴ از دوره جامع Python 🐍
👍1
🐍 آموزش جامع Python — پست ۲۵۵
تفاوت Mock، MagicMock و NonCallableMock
در تست‌نویسی پایتون، همه اشیای ساختگی رفتار یکسانی ندارند. انتخاب نوع مناسب Mock باعث می‌شود تست‌ها دقیق‌تر باشند و رفتار واقعی برنامه را بهتر شبیه‌سازی کنند.
سه ابزار مهم در unittest.mock عبارت‌اند از:
Mock
MagicMock
NonCallableMock
🔹 ۱. کلاس Mock
Mock برای شبیه‌سازی اشیا و بررسی تعامل با آن‌ها استفاده می‌شود.
from unittest.mock import Mock

user = Mock()

user.name = "Sara"
user.get_age.return_value = 25

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

خروجی:
Sara
25

با return_value مشخص کردیم که فراخوانی get_age() چه نتیجه‌ای داشته باشد.
نکته: ویژگی‌های ساختگی مانند get_age معمولاً هنگام دسترسی ایجاد می‌شوند، مگر اینکه محدودیت‌هایی مانند spec اعمال شده باشد.
🔹 ۲. کلاس MagicMock
MagicMock مانند Mock است، اما تعداد زیادی از متدهای ویژه پایتون (Magic Methods) را نیز پشتیبانی می‌کند.
برای مثال، می‌توانیم رفتار len() و عملگر in را شبیه‌سازی کنیم:
from unittest.mock import MagicMock

items = MagicMock()

items.__len__.return_value = 3
items.__contains__.return_value = True

print(len(items))
print("Python" in items)

خروجی:
3
True

این قابلیت برای تست کدهایی مفید است که با عملیات ویژه پایتون کار می‌کنند.
البته نتیجه‌های بالا ساختگی‌اند؛ این کد یک فهرست واقعی با سه عضو ایجاد نمی‌کند.
🔹 ۳. کلاس NonCallableMock
گاهی می‌خواهیم یک شیء ساختگی داشته باشیم که قابل فراخوانی نباشد؛ یعنی نتوان آن را مانند تابع صدا زد.
from unittest.mock import NonCallableMock

config = NonCallableMock()
config.mode = "production"

print(config.mode)

خروجی:
production

اما این دستور خطا ایجاد می‌کند:
config()

زیرا NonCallableMock برای فراخوانی مانند تابع طراحی نشده است.
🔍 مقایسه سریع
ویژگی
Mock
MagicMock
NonCallableMock
ساخت ویژگی‌های ساختگی
بله
بله
بله
پشتیبانی آماده از بسیاری از متدهای ویژه
محدودتر
بله
محدودتر
قابل فراخوانی مانند تابع
بله
بله
خیر
کاربرد اصلی
شبیه‌سازی وابستگی‌ها
شبیه‌سازی اشیای دارای رفتارهای ویژه
شبیه‌سازی اشیای غیرقابل‌فراخوانی
⚠️ نکته مهم: محدودکردن رفتار ساختگی
اگر بخواهیم Mock فقط ویژگی‌های موجود در یک شیء واقعی را بپذیرد، می‌توانیم از spec استفاده کنیم:
from unittest.mock import Mock


class User:
def get_name(self):
return "Sara"


user = Mock(spec=User)

print(user.get_name())

اما دسترسی به ویژگی تعریف‌نشده‌ای مانند user.get_email() خطا ایجاد می‌کند.
spec نام ویژگی‌ها را محدود می‌کند، ولی لزوماً تمام رفتارها و امضاهای شیء واقعی را اعتبارسنجی نمی‌کند. برای محدودیت سخت‌گیرانه‌تر، spec_set و برای بررسی امضای فراخوانی‌ها، autospec نیز وجود دارند که در ادامه بررسی خواهیم کرد.
🎯 تمرین
یک کلاس ShoppingCart با متد add_item() و متد ویژه __len__() در نظر بگیر.
۱. با Mock فراخوانی add_item() را شبیه‌سازی کن.
۲. با MagicMock مقدار بازگشتی len(cart) را برابر 2 قرار بده.
۳. با NonCallableMock یک تنظیمات ساختگی بساز و بررسی کن که فراخوانی آن مجاز نیست.
پست ۲۵۵ از دوره جامع Python 🐍
👍1
🏛 ۷ آبان | روز بزرگداشت کوروش بزرگ

🇮🇷 کوروش بزرگ؛ نمادی از قدرت، تدبیر و احترام به انسان

کوروش بزرگ، بنیان‌گذار شاهنشاهی هخامنشی، یکی از تأثیرگذارترین فرمانروایان تاریخ ایران باستان بود. او با گسترش قلمرو ایران و ایجاد ساختاری گسترده برای اداره سرزمین‌های گوناگون، نقشی مهم در تاریخ جهان ایفا کرد.

📜 استوانه کوروش که پس از فتح بابل در سال ۵۳۹ پیش از میلاد تهیه شد، از مهم‌ترین آثار تاریخی مرتبط با دوران اوست و اطلاعاتی درباره سیاست‌های حکومتی و بازسازی معابد در بابل ارائه می‌دهد.

✨ یادآوری تاریخ کوروش، فرصتی است برای شناخت بهتر تمدن ایران باستان، میراث فرهنگی و نقش ایران در تاریخ جهان.

تاریخ را بشناسیم، میراثمان را پاس بداریم و آینده را آگاهانه بسازیم. 🇮🇷

📌 نکته: ۷ آبان روز بزرگداشت کوروش است، اما تعطیل رسمی نیست.
1👍1
💻🌍 ۲۹ اکتبر | روز جهانی کدنویسی

🎉 روز جهانی کدنویسی مبارک!

برنامه‌نویسی فقط نوشتن چند خط کد نیست؛ بلکه هنر حل مسئله، تفکر منطقی و تبدیل ایده‌ها به واقعیت است.
هر برنامه‌ای که می‌سازیم، می‌تواند شروع یک تغییر بزرگ باشد؛ از یک اسکریپت ساده پایتون تا ساخت هوش مصنوعی و سیستم‌های پیچیده.

🚀 برای برنامه‌نویس‌ها، هر خط کد فرصتی برای یادگیری و ساختن است.

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

🧠 کمتر فقط مصرف‌کننده فناوری باشیم؛ بیشتر آن را بسازیم!

روز جهانی کدنویسی مبارک؛ آینده را با کد می‌سازیم. 💻
👍2
📚 معرفی کتاب آموزش جامع پایتون 🐍

سلام به همه برنامه‌نویس‌ها! 👋

یک کتاب آموزش پایتون پیدا کردم و گفتم اون رو با شما هم به اشتراک بگذارم تا اگر دوست داشتید، بتونید تهیه‌اش کنید و برای یادگیری بیشتر ازش استفاده کنید.

📖 نسخه الکترونیکی: 🔗 دریافت از طاقچه: https://taaghche.com/book/251603

📦 نسخه فیزیکی: می‌تونید کتاب رو به‌صورت چاپی هم از دیجی‌کالا تهیه کنید.

💙 من هم سعی می‌کنم نسخه PDF کتاب رو خریداری کنم و در اختیار شما قرار بدم تا همه بتونیم از این منبع آموزشی استفاده کنیم.

💻 یادگیری رو ادامه بدید؛ هر قدم کوچیک، شما رو به یک برنامه‌نویس بهتر تبدیل می‌کنه!
👍1
🐍 راهنمای پرسش و رفع اشکال دوره Python
دوستان، اگر در طول دوره در هر بخش از آموزش، کدها، تمرین‌ها یا اجرای برنامه به مشکلی برخوردید، می‌توانید سؤال خودتان را مطرح کنید تا بررسی شود.
💬 چه زمانی پیام بدهیم؟
اگر در مواردی مثل این‌ها مشکل داشتید:
🔹 مفهوم یک مبحث را متوجه نشدید
🔹 کدی که نوشته‌اید اجرا نمی‌شود
🔹 با یک Error / Exception مواجه شدید
🔹 خروجی برنامه با چیزی که انتظار داشتید متفاوت است
🔹 در انجام تمرین‌های دوره مشکل دارید
🔹 نمی‌دانید یک بخش از کد چگونه کار می‌کند
🔹 در نصب Python یا اجرای برنامه‌ها مشکلی دارید
🔹 درباره یک بخش از آموزش نیاز به توضیح بیشتری دارید
🔹 در کد خودتان با یک رفتار غیرمنتظره مواجه شدید
می‌توانید مشکل را مطرح کنید.
📌 برای بررسی سریع‌تر مشکل
لطفاً هنگام ارسال سؤال، تا حد امکان این موارد را هم بفرستید:
1️⃣ کدی که نوشته‌اید
# کد خودتان را اینجا قرار دهید

2️⃣ خطایی که دریافت می‌کنید
اگر Error نمایش داده می‌شود، متن کامل آن را ارسال کنید.
3️⃣ توضیح کوتاه درباره مشکل
مثلاً:
انتظار داشتم برنامه عدد 20 را چاپ کند، اما 0 چاپ می‌شود.

این اطلاعات کمک می‌کند مشکل دقیق‌تر بررسی شود.
🕐 ساعت پاسخ‌گویی
برای پاسخ‌گویی و رفع اشکال، هر روز در بازه:
🌙 ساعت ۱۱ تا ۱۲ شب
در خدمت شما هستیم.
اگر خارج از این ساعت پیام ارسال کردید، ممکن است پاسخ‌گویی با تأخیر انجام شود؛ اما پیام شما بررسی خواهد شد.
🆘 راه ارتباطی
اگر در کد، تمرین، آموزش یا اجرای برنامه به مشکل خوردید، از طریق آیدی زیر پیام دهید:
👉 @JarvisAI_77
لطفاً سؤال خودتان را تا حد امکان واضح و همراه با کد و متن خطا ارسال کنید تا بتوانیم دقیق‌تر راهنمایی کنیم.
📚 هدف این است که هیچ مشکلی در مسیر یادگیری Python باعث متوقف شدن ادامه مسیرتان نشود.
سؤال بپرسید، مشکل را پیدا کنید و با هم حلش کنیم. 🐍
👍2
🐍 آموزش جامع Python — پست ۲۵۶
محدودکردن Mock با spec، spec_set و autospec
در پست قبل دیدیم که Mock می‌تواند ویژگی‌های ساختگی ایجاد کند؛ اما این انعطاف‌پذیری گاهی مشکل‌ساز می‌شود.
برای مثال، ممکن است نام یک متد را اشتباه بنویسیم و Mock بدون هشدار آن را ایجاد کند. در نتیجه، تست با وجود اشتباه در کد همچنان موفق شود!
برای جلوگیری از این مشکل، سه قابلیت مهم داریم.
🔹 ۱. استفاده از spec
با spec مشخص می‌کنیم Mock باید از چه ساختاری پیروی کند.
from unittest.mock import Mock


class User:
def get_name(self):
return "Sara"


user = Mock(spec=User)

user.get_name()

# خطا:
user.get_email()

چون get_email در کلاس User تعریف نشده است، دسترسی به آن خطا ایجاد می‌کند.
🔹 ۲. استفاده از spec_set
spec_set محدودیت بیشتری اعمال می‌کند.
from unittest.mock import Mock


class User:
def get_name(self):
return "Sara"


user = Mock(spec_set=User)

user.get_name()

# خطا:
user.email = "sara@example.com"

در این حالت، علاوه بر جلوگیری از دسترسی به ویژگی‌های تعریف‌نشده، نمی‌توان ویژگی جدیدی به Mock اضافه کرد.
🔹 ۳. استفاده از autospec
قابلیت autospec علاوه بر بررسی ساختار، امضای متدها (Signature) را نیز در نظر می‌گیرد.
from unittest.mock import create_autospec


class Calculator:
def add(self, a, b):
return a + b


calculator = create_autospec(
Calculator,
instance=True
)

calculator.add(10, 20)

# خطا: تعداد آرگومان‌ها اشتباه است
calculator.add(10)

در این مثال، autospec اجازه نمی‌دهد متد add() با تعداد آرگومان‌های نامعتبر فراخوانی شود.
🔍 مقایسه سه قابلیت

⚠️ نکات مهم
spec و spec_set برای جلوگیری از اشتباه در نام ویژگی‌ها مفید هستند.
autospec برای بررسی دقیق‌تر ساختار و امضای فراخوانی‌ها مناسب است.
autospec ممکن است در کلاس‌هایی که ویژگی‌های آن‌ها فقط هنگام اجرای __init__() ساخته می‌شوند، تمام ویژگی‌های نمونه را تشخیص ندهد.
هیچ‌کدام جایگزین تست رفتار واقعی برنامه نیستند.
🎯 تمرین
کلاس زیر را در نظر بگیر:
class Product:
def get_price(self, discount):
return 100 - discount

با create_autospec() یک نمونه ساختگی ایجاد کن و موارد زیر را آزمایش کن:
۱. فراخوانی get_price(20) باید مجاز باشد.
۲. فراخوانی get_price() باید خطا ایجاد کند.
۳. فراخوانی متد تعریف‌نشده get_stock() باید خطا ایجاد کند.
هدف این تمرین، ساخت Mockهایی است که به ساختار واقعی برنامه نزدیک‌تر باشند.
پست ۲۵۶ از دوره جامع Python 🐍
👍1
🐍 آموزش جامع Python — پست ۲۵۷
مدیریت چندین Mock با patch.multiple()
در پروژه‌های بزرگ ممکن است یک ماژول چندین وابستگی خارجی داشته باشد. اگر بخواهیم همه آن‌ها را هنگام تست جایگزین کنیم، استفاده از چندین patch() جداگانه می‌تواند کد را طولانی کند.
در چنین شرایطی می‌توانیم از patch.multiple() استفاده کنیم.
🔹 ۱. آشنایی با patch.multiple()
این ابزار امکان جایگزینی چند ویژگی از یک ماژول یا شیء را به‌صورت هم‌زمان فراهم می‌کند.
فرض کنید فایل services.py را داریم:
def send_email(message):
print("Email:", message)


def send_sms(message):
print("SMS:", message)


def notify_user(message):
send_email(message)
send_sms(message)

می‌خواهیم هنگام تست، هر دو سرویس ارسال را شبیه‌سازی کنیم.
🔹 ۲. نوشتن تست
import unittest
from unittest.mock import patch, DEFAULT

import services


class TestNotification(unittest.TestCase):

@patch.multiple(
services,
send_email=DEFAULT,
send_sms=DEFAULT
)
def test_notify(self, send_email, send_sms):

services.notify_user("Welcome!")

send_email.assert_called_once_with("Welcome!")
send_sms.assert_called_once_with("Welcome!")


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

🔍 بررسی کد
patch.multiple()
چند ویژگی از ماژول services را به‌طور هم‌زمان جایگزین می‌کند.
DEFAULT
به patch.multiple() می‌گوید برای این ویژگی، یک Mock پیش‌فرض ایجاد کند.
send_email و send_sms
در این مثال، Mockهای ایجادشده به‌عنوان آرگومان به متد تست تحویل داده می‌شوند.
assert_called_once_with()
بررسی می‌کند هر سرویس دقیقاً یک بار با آرگومان مورد انتظار فراخوانی شده باشد.
🔹 ۳. استفاده با with
می‌توانیم به‌جای Decorator از ساختار with نیز استفاده کنیم:
with patch.multiple(
services,
send_email=DEFAULT,
send_sms=DEFAULT
) as mocks:

services.notify_user("Welcome!")

mocks["send_email"].assert_called_once()
mocks["send_sms"].assert_called_once()

در این روش، دیکشنری mocks شامل Mockهای ساخته‌شده است.
⚠️ نکته مهم
جایگزینی‌ها فقط در محدوده اجرای Patch فعال هستند.
پس از خروج از بلوک with یا پایان تست، ویژگی‌های اصلی بازگردانده می‌شوند.
اگر فقط یک وابستگی دارید، معمولاً patch() ساده‌تر است.
اگر نام ویژگی‌ها از قبل مشخص باشد، DEFAULT باعث ساخت خودکار Mock می‌شود.
🎯 تمرین
ماژولی با سه تابع زیر در نظر بگیر:
save_user()
send_email()
send_notification()

تابعی به نام register_user() بنویس که هر سه عملیات را اجرا کند.
سپس با patch.multiple() هر سه وابستگی را جایگزین کن و بررسی کن که هرکدام دقیقاً یک بار فراخوانی شده‌اند.
پست ۲۵۷ از دوره جامع Python 🐍
👍1
🐍 آموزش جامع Python — پست ۲۵۸
مدیریت موقت تغییرات با patch.dict()
در تست‌نویسی گاهی لازم است مقادیر یک دیکشنری را موقتاً تغییر دهیم؛ مثلاً متغیرهای محیطی (Environment Variables)، تنظیمات برنامه یا داده‌های پیکربندی.
برای این کار می‌توانیم از patch.dict() در ماژول unittest.mock استفاده کنیم.
🔹 ۱. آشنایی با patch.dict()
فرض کنید برنامه ما تنظیمات را از یک دیکشنری دریافت می‌کند:
settings = {
"DEBUG": False,
"LANGUAGE": "en"
}

می‌خواهیم هنگام اجرای تست، مقدار DEBUG را به True تغییر دهیم؛ بدون اینکه تنظیمات اصلی برای همیشه تغییر کنند.
from unittest.mock import patch

settings = {
"DEBUG": False,
"LANGUAGE": "en"
}

with patch.dict(settings, {"DEBUG": True}):
print(settings["DEBUG"])

print(settings["DEBUG"])

خروجی:
True
False

پس از پایان بلوک with، وضعیت اولیه دیکشنری بازیابی می‌شود.
🔹 ۲. استفاده برای تست متغیرهای محیطی
یکی از کاربردهای مهم این ابزار، مدیریت متغیرهای محیطی با os.environ است.
فرض کنید تابع زیر را داریم:
import os


def get_app_mode():
return os.getenv("APP_MODE", "development")

حالا می‌توانیم بدون تغییر دائمی تنظیمات سیستم، آن را آزمایش کنیم:
import unittest
import os
from unittest.mock import patch

from app import get_app_mode


class TestAppMode(unittest.TestCase):

def test_production_mode(self):
with patch.dict(
os.environ,
{"APP_MODE": "production"}
):
self.assertEqual(
get_app_mode(),
"production"
)

def test_default_mode(self):
with patch.dict(os.environ, {}, clear=True):
self.assertEqual(
get_app_mode(),
"development"
)


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

🔍 بررسی پارامترها
آرگومان اول: دیکشنری موردنظر برای تغییر.
آرگومان دوم: مقادیری که باید موقتاً اضافه یا جایگزین شوند.
clear=True: ابتدا محتوای دیکشنری را پاک می‌کند و سپس مقادیر جدید را قرار می‌دهد.
در تست دوم، از clear=True استفاده کردیم تا مطمئن شویم متغیر APP_MODE از قبل در محیط وجود ندارد و نتیجه تست به تنظیمات سیستم وابسته نیست.
⚠️ نکات مهم
۱. تغییرات patch.dict() پس از پایان محدوده Patch بازگردانده می‌شوند.
۲. این ابزار برای دیکشنری‌های معمولی و نگاشت‌هایی مانند os.environ کاربرد دارد.
۳. اگر چند تست هم‌زمان به یک متغیر محیطی دسترسی داشته باشند، تغییرات موقت ممکن است باعث تداخل شوند؛ بنابراین مدیریت تست‌های موازی اهمیت دارد.
۴. clear=True تمام مقادیر قبلی را در محدوده Patch حذف می‌کند؛ پس هنگام استفاده از آن دقت کنید.
🎯 تمرین
تابعی به نام get_database_url() بنویس که مقدار متغیر محیطی DATABASE_URL را برگرداند.
اگر این متغیر وجود نداشت، مقدار زیر را برگرداند:
sqlite:///app.db

سپس با patch.dict() دو تست بنویس:
وقتی DATABASE_URL مقدار مشخصی دارد، همان مقدار برگردد.
وقتی این متغیر وجود ندارد، مقدار پیش‌فرض برگردد.
پست ۲۵۸ از دوره جامع Python 🐍
👍1