📢 اطلاعیه درباره روند انتشار آموزشهای Python 🐍
دوستان عزیز، همراهان دوره جامع Python،
با توجه به حجم مطالبی که تا اینجا در دوره منتشر شده، تصمیم گرفتیم فعلاً سرعت انتشار آموزشهای جدید را کمی کاهش دهیم تا فرصت کافی برای مطالعه، مرور و تمرین مطالب قبلی داشته باشید.
🎯 هدف ما فقط انتشار مطالب بیشتر نیست؛ هدف این است که مطالب را بهخوبی یاد بگیرید و بتوانید در پروژههای واقعی از آنها استفاده کنید.
پیشنهاد میکنیم در این مدت:
🔹 پستهای قبلی را با دقت مرور کنید.
🔹 مثالهای کد را خودتان اجرا کنید.
🔹 تمرینهای هر بخش را حل کنید.
🔹 سعی کنید بدون نگاه کردن به آموزش، کدها را دوباره بنویسید.
🔹 اگر بخشی را خوب متوجه نشدهاید، به همان قسمت برگردید و دوباره مطالعه کنید.
⏳ این کاهش سرعت موقتی است و به معنی توقف دوره نیست. آموزشها ادامه خواهند داشت؛ فقط میخواهیم فرصت بیشتری برای یادگیری عمیقتر و تثبیت مطالب داشته باشید.
از همراهی و حمایت شما سپاسگزاریم. ❤️
آرامتر پیش رفتن، اما عمیقتر یاد گرفتن؛ اصل مهم این مسیر است.
🐍 آموزش جامع Python
دوستان عزیز، همراهان دوره جامع Python،
با توجه به حجم مطالبی که تا اینجا در دوره منتشر شده، تصمیم گرفتیم فعلاً سرعت انتشار آموزشهای جدید را کمی کاهش دهیم تا فرصت کافی برای مطالعه، مرور و تمرین مطالب قبلی داشته باشید.
🎯 هدف ما فقط انتشار مطالب بیشتر نیست؛ هدف این است که مطالب را بهخوبی یاد بگیرید و بتوانید در پروژههای واقعی از آنها استفاده کنید.
پیشنهاد میکنیم در این مدت:
🔹 پستهای قبلی را با دقت مرور کنید.
🔹 مثالهای کد را خودتان اجرا کنید.
🔹 تمرینهای هر بخش را حل کنید.
🔹 سعی کنید بدون نگاه کردن به آموزش، کدها را دوباره بنویسید.
🔹 اگر بخشی را خوب متوجه نشدهاید، به همان قسمت برگردید و دوباره مطالعه کنید.
⏳ این کاهش سرعت موقتی است و به معنی توقف دوره نیست. آموزشها ادامه خواهند داشت؛ فقط میخواهیم فرصت بیشتری برای یادگیری عمیقتر و تثبیت مطالب داشته باشید.
از همراهی و حمایت شما سپاسگزاریم. ❤️
آرامتر پیش رفتن، اما عمیقتر یاد گرفتن؛ اصل مهم این مسیر است.
🐍 آموزش جامع Python
👍1
🔥 یه کانال پیدا کردم که از بچههای همین کانال خودمونه! 😎
داخلش هم چیزای خوب و کاربردی پیدا میشه، هم یه سری چیزای عجیب و باحال 🤯
مثلاً تو یکی از پستهاش نوشته: «آموزش داشتن دو تا سیستمعامل برای کامپیوتر» 😳
مگه همچین چیزی داریم؟! 😅
شاید داشته باشیم… 😏
خواستید یه سر بزنید، ضرر نمیکنید 👇
https://t.me/whyitinfo
هر چی تو عمرم ندیده بودم اینجا دیدم 😁
🐍 آموزش جامع Python
داخلش هم چیزای خوب و کاربردی پیدا میشه، هم یه سری چیزای عجیب و باحال 🤯
مثلاً تو یکی از پستهاش نوشته: «آموزش داشتن دو تا سیستمعامل برای کامپیوتر» 😳
مگه همچین چیزی داریم؟! 😅
شاید داشته باشیم… 😏
خواستید یه سر بزنید، ضرر نمیکنید 👇
https://t.me/whyitinfo
هر چی تو عمرم ندیده بودم اینجا دیدم 😁
🐍 آموزش جامع Python
Telegram
قاطی پاتیهای ذهنم
اللهُمَّ سَلِّم و تَمِّم
آموزش پایتون و....هرچی که بلدم.🩷
یاد میگیریم، اشتباه میکنیم و دوباره یاد میگیریم.
جهت ارتباط با من:
@itsmeitM
آموزش پایتون و....هرچی که بلدم.🩷
یاد میگیریم، اشتباه میکنیم و دوباره یاد میگیریم.
جهت ارتباط با من:
@itsmeitM
10👍2
🐍 آموزش جامع Python — پست ۲۴۱
ساخت تستهای یکپارچه برای Repository در SQLite
در پستهای قبلی یاد گرفتیم چطور با
حالا میخواهیم بررسی کنیم که آیا چند بخش برنامه، وقتی با هم کار میکنند، درست عمل میکنند یا نه. به این روش Integration Testing میگوییم.
🔹 تفاوت تست واحد و تست یکپارچه
Unit Test: یک بخش کوچک از برنامه را بهصورت مستقل بررسی میکند.
Integration Test: تعامل چند بخش را با یکدیگر بررسی میکند؛ مثلاً Repository و دیتابیس SQLite.
🔹 مثال عملی
فرض کنیم فایل
حالا یک فایل به نام
🔹 این تست چه کاری انجام میدهد؟
۱. یک دیتابیس موقت در حافظه ایجاد میکند.
۲. جدول کاربران را میسازد.
۳. کاربر جدیدی اضافه میکند.
۴. همان کاربر را با شناسهاش میخواند.
۵. بررسی میکند که شناسه و نام بازیابیشده صحیح باشند.
۶. پس از پایان تست، اتصال را میبندد.
استفاده از
🔹 اجرای تست
در ترمینال، از پوشه پروژه اجرا کن:
اگر تست موفق باشد، نتیجهای مشابه زیر مشاهده میکنی:
⚠️ نکته مهم
این تست فقط تعامل Repository با SQLite را بررسی میکند. اگر بعداً منطق اصلی برنامه، اعتبارسنجی یا مدیریت خطاها را نیز اضافه کردی، بهتر است برای آنها تستهای جداگانه بنویسی.
🧠 خلاصه
در تست یکپارچه، بهجای بررسی صرف یک تابع، چند بخش واقعی برنامه را کنار هم آزمایش میکنیم.
🎯 تمرین
یک تست جدید به نام
پست ۲۴۱ از دوره جامع Python 🐍
ساخت تستهای یکپارچه برای Repository در SQLite
در پستهای قبلی یاد گرفتیم چطور با
Repository Pattern عملیات دیتابیس را از منطق اصلی برنامه جدا کنیم و با unittest تست بنویسیم.حالا میخواهیم بررسی کنیم که آیا چند بخش برنامه، وقتی با هم کار میکنند، درست عمل میکنند یا نه. به این روش Integration Testing میگوییم.
🔹 تفاوت تست واحد و تست یکپارچه
Unit Test: یک بخش کوچک از برنامه را بهصورت مستقل بررسی میکند.
Integration Test: تعامل چند بخش را با یکدیگر بررسی میکند؛ مثلاً Repository و دیتابیس SQLite.
🔹 مثال عملی
فرض کنیم فایل
user_repository.py شامل کلاس زیر است:class UserRepository:
def __init__(self, connection):
self.connection = connection
def create_table(self):
self.connection.execute("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
)
""")
def add_user(self, name):
cursor = self.connection.execute(
"INSERT INTO users (name) VALUES (?)",
(name,)
)
return cursor.lastrowid
def get_by_id(self, user_id):
return self.connection.execute(
"SELECT id, name FROM users WHERE id = ?",
(user_id,)
).fetchone()
حالا یک فایل به نام
test_user_repository.py بساز:import sqlite3
import unittest
from user_repository import UserRepository
class TestUserRepository(unittest.TestCase):
def setUp(self):
self.connection = sqlite3.connect(":memory:")
self.connection.row_factory = sqlite3.Row
self.repository = UserRepository(self.connection)
self.repository.create_table()
def tearDown(self):
self.connection.close()
def test_add_and_get_user(self):
user_id = self.repository.add_user("Ali")
user = self.repository.get_by_id(user_id)
self.assertIsNotNone(user)
self.assertEqual(user["id"], user_id)
self.assertEqual(user["name"], "Ali")
if __name__ == "__main__":
unittest.main()
🔹 این تست چه کاری انجام میدهد؟
۱. یک دیتابیس موقت در حافظه ایجاد میکند.
۲. جدول کاربران را میسازد.
۳. کاربر جدیدی اضافه میکند.
۴. همان کاربر را با شناسهاش میخواند.
۵. بررسی میکند که شناسه و نام بازیابیشده صحیح باشند.
۶. پس از پایان تست، اتصال را میبندد.
استفاده از
:memory: باعث میشود تست به دیتابیس واقعی برنامه وابسته نباشد و اطلاعات آزمایشی در فایل اصلی ذخیره نشوند.🔹 اجرای تست
در ترمینال، از پوشه پروژه اجرا کن:
python -m unittest -v
اگر تست موفق باشد، نتیجهای مشابه زیر مشاهده میکنی:
test_add_and_get_user ... ok
Ran 1 test
OK
⚠️ نکته مهم
این تست فقط تعامل Repository با SQLite را بررسی میکند. اگر بعداً منطق اصلی برنامه، اعتبارسنجی یا مدیریت خطاها را نیز اضافه کردی، بهتر است برای آنها تستهای جداگانه بنویسی.
🧠 خلاصه
در تست یکپارچه، بهجای بررسی صرف یک تابع، چند بخش واقعی برنامه را کنار هم آزمایش میکنیم.
🎯 تمرین
یک تست جدید به نام
test_missing_user بنویس که شناسهای ناموجود را به get_by_id() بدهد و بررسی کند که نتیجه None است.پست ۲۴۱ از دوره جامع Python 🐍
👍1
🐍 آموزش جامع Python — پست ۲۴۲
استفاده از
در پست قبل تست یکپارچه برای Repository و SQLite نوشتیم.
اما گاهی میخواهیم یک بخش از برنامه را بدون اجرای وابستگیهای واقعی آن آزمایش کنیم. برای این کار میتوانیم از ماژول استاندارد
به این روش، شبیهسازی وابستگیها یا Mocking میگوییم.
🔹 چرا Mock لازم است؟
فرض کنید تابعی داریم که برای دریافت اطلاعات به یک سرویس خارجی متصل میشود. هنگام تست، شاید نخواهیم واقعاً به آن سرویس متصل شویم.
با Mock میتوانیم نتیجه مورد انتظار را شبیهسازی کنیم و رفتار تابع خودمان را مستقل آزمایش کنیم.
🔹 اولین Mock با
خروجی:
در این مثال:
بدون نیاز به پیادهسازی واقعی سرویس، میتوانیم رفتار آن را آزمایش کنیم.
🔹 تست با
فرض کنیم تابع زیر در فایل
این تابع به یک Repository وابسته است، اما برای تست آن نیازی به دیتابیس واقعی نداریم.
فایل
🔹 متد
این متد بررسی میکند که یک متد دقیقاً یک بار و با آرگومانهای مشخص فراخوانی شده باشد:
اگر متد اصلاً فراخوانی نشده باشد، چند بار اجرا شده باشد یا آرگومان دیگری دریافت کرده باشد، تست شکست میخورد.
⚠️ نکته مهم
Mock جایگزین تمام تستهای واقعی نیست. در این مثال بررسی میکنیم که منطق
بنابراین:
برای منطق مستقل، از Unit Test و Mock استفاده کن.
برای بررسی تعامل واقعی با SQLite، از Integration Test استفاده کن.
🧠 خلاصه
🎯 تمرین
تابعی به نام
با
اگر کاربر وجود داشت،
اگر کاربر وجود نداشت، هیچ حذفی انجام ندهد و
سپس با
پست ۲۴۲ از دوره جامع Python 🐍
استفاده از
unittest.mock برای شبیهسازی وابستگیهادر پست قبل تست یکپارچه برای Repository و SQLite نوشتیم.
اما گاهی میخواهیم یک بخش از برنامه را بدون اجرای وابستگیهای واقعی آن آزمایش کنیم. برای این کار میتوانیم از ماژول استاندارد
unittest.mock استفاده کنیم.به این روش، شبیهسازی وابستگیها یا Mocking میگوییم.
🔹 چرا Mock لازم است؟
فرض کنید تابعی داریم که برای دریافت اطلاعات به یک سرویس خارجی متصل میشود. هنگام تست، شاید نخواهیم واقعاً به آن سرویس متصل شویم.
با Mock میتوانیم نتیجه مورد انتظار را شبیهسازی کنیم و رفتار تابع خودمان را مستقل آزمایش کنیم.
🔹 اولین Mock با
Mockfrom unittest.mock import Mock
service = Mock()
service.get_username.return_value = "Ali"
result = service.get_username()
print(result)
خروجی:
Ali
در این مثال:
Mock() یک شیء شبیهسازیشده میسازد.return_value نتیجهای را تعیین میکند که فراخوانی متد برمیگرداند.بدون نیاز به پیادهسازی واقعی سرویس، میتوانیم رفتار آن را آزمایش کنیم.
🔹 تست با
unittestفرض کنیم تابع زیر در فایل
user_service.py قرار دارد:def get_display_name(repository, user_id):
user = repository.get_by_id(user_id)
if user is None:
return "Unknown user"
return user["name"]
این تابع به یک Repository وابسته است، اما برای تست آن نیازی به دیتابیس واقعی نداریم.
فایل
test_user_service.py:import unittest
from unittest.mock import Mock
from user_service import get_display_name
class TestUserService(unittest.TestCase):
def test_existing_user(self):
repository = Mock()
repository.get_by_id.return_value = {
"id": 1,
"name": "Ali"
}
result = get_display_name(repository, 1)
self.assertEqual(result, "Ali")
repository.get_by_id.assert_called_once_with(1)
def test_missing_user(self):
repository = Mock()
repository.get_by_id.return_value = None
result = get_display_name(repository, 99)
self.assertEqual(result, "Unknown user")
if __name__ == "__main__":
unittest.main()
🔹 متد
assert_called_once_with()این متد بررسی میکند که یک متد دقیقاً یک بار و با آرگومانهای مشخص فراخوانی شده باشد:
repository.get_by_id.assert_called_once_with(1)
اگر متد اصلاً فراخوانی نشده باشد، چند بار اجرا شده باشد یا آرگومان دیگری دریافت کرده باشد، تست شکست میخورد.
⚠️ نکته مهم
Mock جایگزین تمام تستهای واقعی نیست. در این مثال بررسی میکنیم که منطق
get_display_name() درست کار میکند؛ اما صحت Queryهای SQLite را بررسی نمیکنیم.بنابراین:
برای منطق مستقل، از Unit Test و Mock استفاده کن.
برای بررسی تعامل واقعی با SQLite، از Integration Test استفاده کن.
🧠 خلاصه
Mock به ما اجازه میدهد وابستگیهای یک تابع را شبیهسازی کنیم و رفتار خود تابع را بدون اجرای واقعی آن وابستگیها آزمایش کنیم.🎯 تمرین
تابعی به نام
delete_user_if_exists(repository, user_id) بنویس که:با
repository.get_by_id(user_id) وجود کاربر را بررسی کند.اگر کاربر وجود داشت،
repository.delete_user(user_id) را فراخوانی کند و True برگرداند.اگر کاربر وجود نداشت، هیچ حذفی انجام ندهد و
False برگرداند.سپس با
Mock برای هر دو حالت تست بنویس.پست ۲۴۲ از دوره جامع Python 🐍
1👍1
🐍 آموزش جامع Python — پست ۲۴۳
تستکردن خطاها با
در پستهای قبلی با Exceptionهای اختصاصی و تستنویسی با
اما تست موفقیتآمیز بودن یک عملیات کافی نیست؛ گاهی باید مطمئن شویم برنامه هنگام دریافت ورودی نامعتبر، خطای درست را ایجاد میکند.
برای این کار از
🔹 مثال ساده
در این مثال، تست زمانی موفق است که فراخوانی
اگر تابع هیچ خطایی ایجاد نکند، تست شکست میخورد.
🔹 بررسی متن خطا
گاهی فقط نوع خطا کافی نیست و میخواهیم پیام آن را هم بررسی کنیم:
متغیر
ویژگی
🔹 کاربرد در پروژه SQLite
فرض کنیم تابعی برای اعتبارسنجی سن کاربران داریم:
حالا میتوانیم حالتهای نامعتبر را تست کنیم:
در این تستها بررسی میکنیم که ورودی نامعتبر با نوع خطای مورد انتظار رد شود.
🔹 تفاوت
⚠️ نکته مهم
نوع خطای مورد انتظار را دقیق انتخاب کن. اگر انتظار
همچنین فقط همان دستوری را داخل بلوک
🧠 خلاصه
این الگو برای تست اعتبارسنجی ورودیها، خطاهای اختصاصی و رفتارهای نامعتبر در پروژههای Python کاربرد زیادی دارد.
🎯 تمرین
برای تابع
۱. سن
۲. سن
۳. مقدار
پست ۲۴۳ از دوره جامع Python 🐍
تستکردن خطاها با
assertRaisesدر پستهای قبلی با Exceptionهای اختصاصی و تستنویسی با
unittest آشنا شدیم.اما تست موفقیتآمیز بودن یک عملیات کافی نیست؛ گاهی باید مطمئن شویم برنامه هنگام دریافت ورودی نامعتبر، خطای درست را ایجاد میکند.
برای این کار از
assertRaises() استفاده میکنیم.🔹 مثال ساده
import unittest
def divide(a, b):
if b == 0:
raise ValueError("Divisor cannot be zero")
return a / b
class TestDivide(unittest.TestCase):
def test_divide_by_zero(self):
with self.assertRaises(ValueError):
divide(10, 0)
if __name__ == "__main__":
unittest.main()
در این مثال، تست زمانی موفق است که فراخوانی
divide(10, 0) خطای ValueError ایجاد کند.اگر تابع هیچ خطایی ایجاد نکند، تست شکست میخورد.
🔹 بررسی متن خطا
گاهی فقط نوع خطا کافی نیست و میخواهیم پیام آن را هم بررسی کنیم:
def test_error_message(self):
with self.assertRaises(ValueError) as context:
divide(10, 0)
self.assertEqual(
str(context.exception),
"Divisor cannot be zero"
)
متغیر
context اطلاعات Exception رخداده را در اختیارمان قرار میدهد.ویژگی
context.exception خود شیء خطاست و با str() میتوانیم پیام آن را بخوانیم.🔹 کاربرد در پروژه SQLite
فرض کنیم تابعی برای اعتبارسنجی سن کاربران داریم:
def validate_age(age):
if not isinstance(age, int) or isinstance(age, bool):
raise TypeError("Age must be an integer")
if not 1 <= age <= 120:
raise ValueError("Age must be between 1 and 120")
return age
حالا میتوانیم حالتهای نامعتبر را تست کنیم:
class TestAgeValidation(unittest.TestCase):
def test_invalid_type(self):
with self.assertRaises(TypeError):
validate_age("twenty")
def test_invalid_range(self):
with self.assertRaises(ValueError):
validate_age(150)
def test_boolean_is_rejected(self):
with self.assertRaises(TypeError):
validate_age(True)
در این تستها بررسی میکنیم که ورودی نامعتبر با نوع خطای مورد انتظار رد شود.
🔹 تفاوت
assertRaises و try/excepttry/except برای مدیریت خطا در اجرای عادی برنامه استفاده میشود.assertRaises بررسی میکند که هنگام تست، یک عملیات خطای مشخصی ایجاد کند.⚠️ نکته مهم
نوع خطای مورد انتظار را دقیق انتخاب کن. اگر انتظار
ValueError داری اما برنامه بهجای آن TypeError ایجاد کند، تست نباید موفق محسوب شود.همچنین فقط همان دستوری را داخل بلوک
assertRaises قرار بده که انتظار داری خطا ایجاد کند؛ در غیر این صورت ممکن است خطای بخش دیگری از کد باعث قبولی اشتباه تست شود.🧠 خلاصه
with self.assertRaises(ValueError):
some_function()
این الگو برای تست اعتبارسنجی ورودیها، خطاهای اختصاصی و رفتارهای نامعتبر در پروژههای Python کاربرد زیادی دارد.
🎯 تمرین
برای تابع
validate_age() سه تست بنویس:۱. سن
25 باید پذیرفته شود.۲. سن
0 باید ValueError ایجاد کند.۳. مقدار
"25" باید TypeError ایجاد کند.پست ۲۴۳ از دوره جامع Python 🐍
👍1
🐍 آموزش جامع Python — پست ۲۴۴
استفاده از
در پست ۲۴۲ با
این کار زمانی مفید است که تابع مورد آزمایش به سرویس ایمیل، API خارجی یا بخش دیگری از برنامه وابسته باشد.
🔹 مثال عملی
فرض کنیم دو فایل داریم.
فایل
فایل
تابع
🔹 نوشتن تست با
فایل
🔍 این کد چگونه کار میکند؟
متغیر
پس از پایان تست، جایگزینی موقت برداشته میشود.
⚠️ نکته بسیار مهم
در این مثال باید بنویسیم:
نه:
چرا؟ چون در
قاعده اصلی: چیزی را که کد مورد آزمایش از آن استفاده میکند، در همان محل استفاده جایگزین کن.
🎯 تمرین
تابع زیر را در فایل
یک تست بنویس که با
پست ۲۴۴ از دوره جامع Python 🐍
استفاده از
patch() برای شبیهسازی وابستگیها در تستدر پست ۲۴۲ با
Mock آشنا شدیم. حالا میخواهیم از ابزار patch() استفاده کنیم تا هنگام اجرای تست، یک تابع واقعی را موقتاً با یک نمونه ساختگی جایگزین کنیم.این کار زمانی مفید است که تابع مورد آزمایش به سرویس ایمیل، API خارجی یا بخش دیگری از برنامه وابسته باشد.
🔹 مثال عملی
فرض کنیم دو فایل داریم.
فایل
notifications.pydef send_email(address, message):
print(f"Sending email to {address}")
فایل
registration.pyfrom notifications import send_email
def register_user(email):
# ثبتنام کاربر در این قسمت انجام میشود
send_email(email, "Welcome!")
تابع
register_user() برای ارسال ایمیل به send_email() وابسته است. در تست نمیخواهیم ایمیلی واقعاً ارسال شود؛ فقط میخواهیم بررسی کنیم که تابع ارسال ایمیل بهدرستی فراخوانی شده است.🔹 نوشتن تست با
patch()فایل
test_registration.pyimport unittest
from unittest.mock import patch
from registration import register_user
class TestRegistration(unittest.TestCase):
@patch("registration.send_email")
def test_register_sends_email(self, mock_send_email):
register_user("user@example.com")
mock_send_email.assert_called_once_with(
"user@example.com",
"Welcome!"
)
if __name__ == "__main__":
unittest.main()
🔍 این کد چگونه کار میکند؟
patch() تابع send_email را در ماژول registration موقتاً جایگزین میکند.متغیر
mock_send_email نمونه ساختگی ایجادشده را دریافت میکند.assert_called_once_with() بررسی میکند که تابع دقیقاً یک بار و با آرگومانهای مورد انتظار فراخوانی شده باشد.پس از پایان تست، جایگزینی موقت برداشته میشود.
⚠️ نکته بسیار مهم
در این مثال باید بنویسیم:
@patch("registration.send_email")نه:
@patch("notifications.send_email")چرا؟ چون در
registration.py تابع با دستور from notifications import send_email وارد شده است؛ بنابراین هنگام اجرای register_user()، نام send_email از فضای نام ماژول registration خوانده میشود.قاعده اصلی: چیزی را که کد مورد آزمایش از آن استفاده میکند، در همان محل استفاده جایگزین کن.
🎯 تمرین
تابع زیر را در فایل
user_service.py در نظر بگیر:from notifications import send_email
def notify_user(email):
send_email(email, "Your account is ready!")
یک تست بنویس که با
patch() ارسال واقعی ایمیل را متوقف کند و بررسی کند که send_email دقیقاً یک بار با آرگومانهای زیر فراخوانی شده است:email: مقدار admin@example.commessage: مقدار Your account is ready!پست ۲۴۴ از دوره جامع Python 🐍
👍1
🐍 آموزش جامع Python — پست ۲۴۵
تفاوت
وقتی تعداد تستها زیاد میشود، ممکن است چند تست به دادهها یا منابع اولیه یکسانی نیاز داشته باشند. در کتابخانه
۱. متد
این متد پیش از اجرای هر متد تست اجرا میشود و برای آمادهسازی شرایط تست کاربرد دارد.
۲. متد
این متد پس از اجرای هر متد تست، در صورت موفقیت یا شکست آن، اجرا میشود و برای پاکسازی منابع کاربرد دارد؛ البته اگر اجرای
🔹 مثال عملی
🔍 ترتیب اجرا
برای هر تست، ترتیب کلی به این شکل است:
بنابراین، برای دو متد تست بالا،
نکته: ترتیب اجرای متدهای تست را نباید بدون دلیل مشخص، ترتیب نوشتن آنها در کلاس فرض کنید.
🔹 چرا این روش مفید است؟
فرض کنید چند تست به یک فهرست اولیه نیاز دارند. بهجای تکرار ساخت فهرست در هر تست، آن را در
همچنین هر تست، نمونه جداگانهای از کلاس تست دریافت میکند؛ بنابراین تغییر
🔸 تفاوت با
اگر بخواهیم آمادهسازی فقط یک بار برای کل کلاس تست انجام شود، میتوانیم از
این روش برای منابع پرهزینهای مناسب است که قرار است بین تستهای یک کلاس مشترک باشند؛ البته باید مراقب تغییر دادههای مشترک باشیم.
🎯 تمرین
کلاسی به نام
۱. در
۲. در یک تست، تعداد محصولات را بررسی کند.
۳. در تست دیگر، مجموع قیمت محصولات را بررسی کند.
۴. در
پست ۲۴۵ از دوره جامع 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 تا کد تفاوت قائل بشه:
اما داستان به همینجا ختم نمیشه
کدتون باید برای هر برنامه و ورودی دلخواه جواب درست بده و خودشم همیشه تو زمان محدود به جواب برسه
یه برنامه بنویسید که یه کد دریافت کنه و تشخیص بده که اجرای اون برنامه بالاخره تموم میشه یا برای همیشه ادامه پیدا میکنه
مثلا برنامتون باید بین این 2 تا کد تفاوت قائل بشه:
#1
print("Hello Dev FA")
#2
while True:
pass
اما داستان به همینجا ختم نمیشه
کدتون باید برای هر برنامه و ورودی دلخواه جواب درست بده و خودشم همیشه تو زمان محدود به جواب برسه
13👍1
🐍 آموزش جامع Python — پست ۲۴۶
مدیریت منابع تست با
در پست قبل با
این متد برای ثبت عملیات پاکسازی استفاده میشود؛ عملیاتی که پس از پایان تست باید اجرا شوند، مانند بستن فایل یا آزادسازی یک منبع.
🔹 مثال عملی
فرض کنید تست ما یک فایل موقت باز میکند. میخواهیم مطمئن شویم فایل پس از پایان تست بسته میشود.
🔍 کد چگونه کار میکند؟
پس از پایان هر تست، تابع ثبتشده اجرا میشود؛ حتی اگر تست با شکست مواجه شود یا اجرای آن با خطا متوقف شود.
از آنجا که
🔸 تفاوت
ویژگی
روش استفاده
بازنویسی یک متد
ثبت یک یا چند تابع
تعداد عملیات پاکسازی
میتوان چند عملیات را در آن نوشت
میتوان چند تابع جداگانه ثبت کرد
ترتیب اجرا
پس از تست، در صورت موفقیت
توابع ثبتشده با ترتیب معکوس ثبت اجرا میشوند
خطا در
ممکن است اجرا نشود
توابعی که پیش از خطا ثبت شدهاند، اجرا میشوند
نکته مهم: توابع
🎯 تمرین
کلاسی برای تست یک منبع فرضی بساز که:
۱. در
۲. با
۳. در تابع پاکسازی، یک پیام چاپ کند.
۴. بررسی کند که دادهها در زمان اجرای تست در دسترس هستند.
هدف این است که تفاوت آمادهسازی تست و پاکسازی خودکار منابع را در عمل درک کنید.
پست ۲۴۶ از دوره جامع 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 — پست ۲۴۷
مدیریت منابع در تستها با
در تستنویسی، بعضی منابع برای هر تست بهصورت جداگانه آماده میشوند؛ اما بعضی دیگر، مانند یک منبع مشترک یا دادههای پرهزینه برای آمادهسازی، بهتر است فقط یک بار برای کل کلاس ایجاد شوند.
در
۱. ساختار اصلی
در این مثال،
۲. چرا از
این دو متد بهجای یک نمونه از کلاس تست، خود کلاس را دریافت میکنند؛ بنابراین پارامتر آنها
اکنون داده از طریق کلاس قابل دسترسی است:
۳. تفاوت با
متد
زمان اجرا
پیش از هر تست
پس از هر تست، اگر
یک بار پیش از تستهای کلاس
پس از پایان اجرای تستهای کلاس
برای مثال، اگر کلاس سه متد تست داشته باشد، در اجرای معمول هر چهار متد مربوط به چرخه عمر به این صورت رفتار میکنند:
البته اگر راهاندازی با خطا مواجه شود یا اجرای تستها به شکل غیرمعمول متوقف شود، این ترتیب ممکن است کامل نشود.
۴. مثال کاربردی با دادههای مشترک
در اینجا دیکشنری یک بار ساخته میشود و همه تستها آن را میخوانند.
نکته مهم: دادههای مشترک ممکن است بین تستها تغییر کنند. اگر یک تست دیکشنری را تغییر دهد، تستهای دیگر نیز میتوانند تغییر را مشاهده کنند. بنابراین، برای دادههای قابلتغییر یا تستهای مستقل، معمولاً ساخت داده در
همچنین
🎯 تمرین
کلاسی به نام
۱. در
۲. در تست اول، قیمت یکی از محصولات را بررسی کند.
۳. در تست دوم، تعداد محصولات را بررسی کند.
۴. در
۵. توضیح بده چرا نباید بدون دلیل، دادههای مشترک را در هر تست تغییر دهیم.
اجرای تستها:
در پست بعدی به سراغ مدیریت خطا هنگام آمادهسازی منابع تست میرویم تا تستها حتی در شرایط شکست نیز پاکسازی قابلاعتمادتری داشته باشند.
پست ۲۴۷ از دوره جامع 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 — پست ۲۴۸
پاکسازی مطمئن منابع با
در پست قبل با
برای این کار،
۱. مشکل
فرض کنید در مرحله آمادهسازی کلاس، یک فایل باز میکنیم و سپس هنگام آمادهسازی منبع دیگری خطایی رخ میدهد.
در این مثال، اگر
۲. راهحل با
بهتر است عملیات پاکسازی را بلافاصله پس از ایجاد منبع ثبت کنیم:
🔍 چه اتفاقی میافتد؟
۱. فایل باز میشود.
۲. تابع
۳. اگر ادامه آمادهسازی کلاس با خطا مواجه شود، عملیات پاکسازی ثبتشده همچنان اجرا میشود.
۴. اگر تستها اجرا شوند، پاکسازی کلاس پس از پایان اجرای تستهای آن انجام میشود.
۳. ترتیب اجرای پاکسازیها
اگر چند عملیات ثبت کنیم:
عملیات پاکسازی به ترتیب معکوس ثبت اجرا میشوند؛ بنابراین ابتدا
این ترتیب برای منابع وابسته به یکدیگر اهمیت دارد. معمولاً باید منبعی که آخر از همه ایجاد شده، زودتر آزاد شود.
۴. تفاوت سه روش
روش
کاربرد
پاکسازی پس از هر تست
پاکسازی مشترک پس از اجرای تستهای کلاس
ثبت عملیات پاکسازی کلاس، حتی اگر
نکته:
🎯 تمرین
کلاسی طراحی کن که در
سپس:
یک تست برای بررسی باز بودن فایل بنویس.
توضیح بده چرا ثبت پاکسازی باید بلافاصله پس از باز کردن فایل انجام شود.
مشخص کن اگر آمادهسازی بعد از باز شدن فایل با خطا مواجه شود، چه اتفاقی میافتد.
پست ۲۴۸ از دوره جامع 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 — پست ۲۴۹
تستکردن خروجی چاپشده با
گاهی تابعی مقدار مشخصی برنمیگرداند، بلکه نتیجه را با
در پایتون میتوانیم با
۱. تابعی برای نمایش اطلاعات
فرض کنید فایل
اجرای زیر:
این متن را چاپ میکند:
اما برای تست خودکار، بهتر است بهجای بررسی دستی ترمینال، خروجی را در تست دریافت کنیم.
۲. استفاده از
۳. بررسی بخشهای مهم کد
در طول تست،
تمام متن ذخیرهشده در جریان را بهصورت رشته برمیگرداند.
۴. چرا در انتهای خروجی
تابع
بنابراین:
معمولاً خروجی ثبتشده زیر را دارد:
اگر مقدار
در این حالت، خروجی دقیق برابر
۵. بررسی خروجی چندخطی
تابع زیر را در نظر بگیر:
تست آن میتواند به این شکل باشد:
توجه کنید که این متد تست باید داخل یک کلاس فرزند
⚠️ نکته مهم
جایگزینی
همچنین برای تست خروجی چاپشده، معمولاً بهتر است متن مورد انتظار را دقیق بررسی کنید؛ فاصلهها و خطهای جدید هم بخشی از خروجی هستند.
🎯 تمرین
تابعی به نام
سپس با
پست ۲۴۹ از دوره جامع 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() و StringIOimport 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 — پست ۲۵۰
تستکردن فایلها و پوشهها با
در پروژههای واقعی، توابع زیادی با فایلها و پوشهها کار میکنند؛ مثلاً خواندن تنظیمات، ذخیره گزارش یا پردازش فایلهای ورودی.
اما اگر تستها مستقیماً فایلهای واقعی پروژه را تغییر دهند، ممکن است اطلاعات مهمی از بین برود یا نتیجه تستها به وضعیت سیستم وابسته شود.
ماژول استاندارد
۱. ایجاد فایل موقت با
خروجی:
در این مثال:
با پایان بلوک
رفتار دقیق حذف فایل موقت به نوع تابع و سیستمعامل وابسته است.
۲. تست یک تابع که از مسیر فایل استفاده میکند
فرض کنید تابع زیر در فایل
میخواهیم بررسی کنیم که تابع محتوای فایل را درست میخواند؛ بدون اینکه به یک فایل تنظیمات واقعی وابسته باشیم.
نوشتن تست
🔍 چرا این روش مناسب است؟
۱.
۲. فایل
۳. تابع واقعی
۴. با پایان بلوک
در این تست از
۳. تفاوت ابزارهای مهم
ابزار
کاربرد
ایجاد فایل موقت برای کار مستقیم با محتوای آن
ایجاد فایل موقت با نام و مسیر قابلدسترسی
ایجاد پوشه موقت برای نگهداری یک یا چند فایل
نگهداری داده در حافظه تا رسیدن به آستانه مشخص و سپس انتقال به فایل موقت
نکته: اگر تابع مورد آزمایش به مسیر واقعی فایل نیاز دارد،
۴. یک اشتباه رایج
اگر تابع شما مسیر نسبی میگیرد، نباید فرض کنید فایل همیشه نسبت به پوشه پروژه پیدا میشود. مسیر نسبی معمولاً نسبت به پوشه کاری فعلی فرایند تفسیر میشود.
استفاده از مسیر صریح ساختهشده با
🎯 تمرین
تابعی به نام
سپس با
پست ۲۵۰ از دوره جامع Python 🐍
تستکردن فایلها و پوشهها با
tempfileدر پروژههای واقعی، توابع زیادی با فایلها و پوشهها کار میکنند؛ مثلاً خواندن تنظیمات، ذخیره گزارش یا پردازش فایلهای ورودی.
اما اگر تستها مستقیماً فایلهای واقعی پروژه را تغییر دهند، ممکن است اطلاعات مهمی از بین برود یا نتیجه تستها به وضعیت سیستم وابسته شود.
ماژول استاندارد
tempfile امکان ایجاد فایلها و پوشههای موقت را فراهم میکند تا تستها محیط مستقلتری داشته باشند.۱. ایجاد فایل موقت با
TemporaryFileimport 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 — پست ۲۵۱
تستکردن کدهای ناهمگام با
در پایتون، توابعی که با
کتابخانه استاندارد
🔹 ۱. یک تابع ناهمگام بسازیم
فراخوانی این تابع یک شیء coroutine ایجاد میکند. برای دریافت نتیجه، باید آن را اجرا و منتظر تکمیلش بمانیم.
🔹 ۲. نوشتن تست ناهمگام
🔍 نکات مهم
متد تست ناهمگام باید با
درون متد تست، برای انتظار نتیجه تابع ناهمگام از
در این مثال،
🔹 ۳. بررسی خطا در تابع ناهمگام
میتوانیم خطاهای مورد انتظار را نیز آزمایش کنیم:
تست:
این متد تست باید داخل کلاس
نکته: اگر از
🎯 تمرین
تابع ناهمگام زیر را پیادهسازی و آزمایش کن:
این تابع باید مربع عدد را برگرداند. سپس دو تست بنویس:
۱. بررسی کن که
۲. اگر تصمیم گرفتی ورودی منفی مجاز نباشد، برای آن
اجرای تستها:
پست ۲۵۱ از دوره جامع 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 — پست ۲۵۲
شبیهسازی توابع ناهمگام با
در پست قبل یاد گرفتیم توابع
ابزار
🔹 ۱. چرا به
فرض کنید تابعی برای دریافت اطلاعات کاربر از یک API داریم. هنگام تست نمیخواهیم درخواست واقعی اینترنتی ارسال کنیم؛ فقط میخواهیم رفتار تابع خودمان را بررسی کنیم.
فایل
این تابع به یک متد ناهمگام به نام
🔹 ۲. نوشتن تست با
🔍 توضیح کد
در این تست، هیچ درخواست واقعی به API ارسال نمیشود.
🔸 تفاوت
متد
چه چیزی را بررسی میکند؟
تابع یک بار با آرگومانهای مشخص فراخوانی شده است
فراخوانی ناهمگام یک بار با آرگومانهای مشخص مورد
برای بررسی رفتار ناهمگام، معمولاً
⚠️ نکته مهم
در کد بالا از
🎯 تمرین
تابعی به نام
سپس با
۱. نتیجه ساختگی
۲. نتیجه تابع را بررسی کند.
۳. تأیید کند
پست ۲۵۲ از دوره جامع 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() وابسته است.🔹 ۲. نوشتن تست با
AsyncMockimport 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 — پست ۲۵۳
تستکردن فراخوانیها با
در تستنویسی، گاهی فقط دانستن نتیجه یک تابع کافی نیست؛ باید بررسی کنیم تابعهای وابسته با چه آرگومانهایی و چند بار فراخوانی شدهاند.
در
🔹 ۱. بررسی آخرین فراخوانی با
خروجی:
ویژگی
🔹 ۲. بررسی تمام فراخوانیها با
خروجی:
این ویژگی تمام فراخوانیها را به ترتیب وقوع نگه میدارد.
🔹 ۳. بررسی آرگومانها
هر فراخوانی ثبتشده شامل دو بخش است:
مثال:
خروجی:
وجود ویرگول در
🔹 ۴. استفاده در تست واقعی
فرض کنیم تابع زیر را داریم:
تست آن:
در این تست، علاوه بر تعداد فراخوانیها، ترتیب و آرگومانهای هر فراخوانی نیز بررسی میشود.
⚠️ نکته مهم
اگر فقط میخواهید تعداد فراخوانیها را بررسی کنید، از
همچنین
🎯 تمرین
تابعی به نام
سپس با
۱. تعداد فراخوانیها برابر تعداد اعضای فهرست باشد.
۲. آرگومانهای هر فراخوانی درست باشند.
۳. ترتیب فراخوانیها مطابق ترتیب فهرست ورودی باشد.
پست ۲۵۳ از دوره جامع Python 🐍
تستکردن فراخوانیها با
call_args و call_args_listدر تستنویسی، گاهی فقط دانستن نتیجه یک تابع کافی نیست؛ باید بررسی کنیم تابعهای وابسته با چه آرگومانهایی و چند بار فراخوانی شدهاند.
در
unittest.mock ابزارهایی مانند call_args و call_args_list این امکان را فراهم میکنند.🔹 ۱. بررسی آخرین فراخوانی با
call_argsfrom 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_listfrom 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 نباید جای تست رفتار واقعی سیستم را بگیرد؛ Mock برای بررسی تعامل با وابستگیها مناسب است.
🎯 تمرین
تابعی به نام
۱.
۲.
سپس با
پست ۲۵۴ از دوره جامع 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_callsfrom 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 باعث میشود تستها دقیقتر باشند و رفتار واقعی برنامه را بهتر شبیهسازی کنند.
سه ابزار مهم در
🔹 ۱. کلاس
خروجی:
با
نکته: ویژگیهای ساختگی مانند
🔹 ۲. کلاس
برای مثال، میتوانیم رفتار
خروجی:
این قابلیت برای تست کدهایی مفید است که با عملیات ویژه پایتون کار میکنند.
البته نتیجههای بالا ساختگیاند؛ این کد یک فهرست واقعی با سه عضو ایجاد نمیکند.
🔹 ۳. کلاس
گاهی میخواهیم یک شیء ساختگی داشته باشیم که قابل فراخوانی نباشد؛ یعنی نتوان آن را مانند تابع صدا زد.
خروجی:
اما این دستور خطا ایجاد میکند:
زیرا
🔍 مقایسه سریع
ویژگی
ساخت ویژگیهای ساختگی
بله
بله
بله
پشتیبانی آماده از بسیاری از متدهای ویژه
محدودتر
بله
محدودتر
قابل فراخوانی مانند تابع
بله
بله
خیر
کاربرد اصلی
شبیهسازی وابستگیها
شبیهسازی اشیای دارای رفتارهای ویژه
شبیهسازی اشیای غیرقابلفراخوانی
⚠️ نکته مهم: محدودکردن رفتار ساختگی
اگر بخواهیم Mock فقط ویژگیهای موجود در یک شیء واقعی را بپذیرد، میتوانیم از
اما دسترسی به ویژگی تعریفنشدهای مانند
🎯 تمرین
یک کلاس
۱. با
۲. با
۳. با
پست ۲۵۵ از دوره جامع Python 🐍
تفاوت
Mock، MagicMock و NonCallableMockدر تستنویسی پایتون، همه اشیای ساختگی رفتار یکسانی ندارند. انتخاب نوع مناسب Mock باعث میشود تستها دقیقتر باشند و رفتار واقعی برنامه را بهتر شبیهسازی کنند.
سه ابزار مهم در
unittest.mock عبارتاند از:MockMagicMockNonCallableMock🔹 ۱. کلاس
MockMock برای شبیهسازی اشیا و بررسی تعامل با آنها استفاده میشود.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 اعمال شده باشد.🔹 ۲. کلاس
MagicMockMagicMock مانند 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 برای فراخوانی مانند تابع طراحی نشده است.🔍 مقایسه سریع
ویژگی
MockMagicMockNonCallableMockساخت ویژگیهای ساختگی
بله
بله
بله
پشتیبانی آماده از بسیاری از متدهای ویژه
محدودتر
بله
محدودتر
قابل فراخوانی مانند تابع
بله
بله
خیر
کاربرد اصلی
شبیهسازی وابستگیها
شبیهسازی اشیای دارای رفتارهای ویژه
شبیهسازی اشیای غیرقابلفراخوانی
⚠️ نکته مهم: محدودکردن رفتار ساختگی
اگر بخواهیم 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