طَبِّقْهاتعلّم المبدأ، وطبّقه بيدكمقرر 245 · ضمان جودة تطبيقات الويب
الدرس 17 · الوحدة 9: تقييم نتائج الاختبارات

المحاكاة والبدائل: Mocks و Stubs

كيف نختبر دالةً تعتمد على الشبكة أو الوقت دون أن نلمسهما فعلًا؟ ⏱ نحو 45 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تفرّق بين Stub و Mock و Fake.
  • تستبدل الاعتمادات الخارجية ببدائل.
  • تستخدم unittest.mock لمراقبة الاستدعاءات.
  • تحاكي الحدود لا المنطق.

١كيف نختبر ما يعتمد على الخارج؟

دالةٌ تستدعي بوابة دفعٍ عبر الإنترنت، أو تقرأ الوقت الحاليّ، أو ترسل بريدًا. كيف نختبرها؟ لا نريد دفعًا حقيقيًّا في كل اختبار، ولا اختبارًا يفشل لأن الشبكة بطيئة. الحلّ: نستبدل الاعتماد الخارجيّ ببديلٍ نتحكّم فيه.

البديلماذا يفعلمثال
Stubيعيد قيمةً ثابتة مُجهَّزةبوابة دفعٍ تعيد «نجح» دائمًا
Mockبديلٌ نراقب كيف اُستُدعِينتأكّد أن «أرسل البريد» نودي مرّة
Fakeنسخةٌ مبسّطة عاملةقاعدة بيانات في الذاكرة

الفائدة: اختبارٌ سريع وحتميّ ومعزول عن العالم الخارجيّ. نختبر منطقنا لا خدمة الدفع، ونتحكّم في كل سيناريو (نجاح، فشل، خطأ شبكة).

٢Stub: استبدل بقيمةٍ ثابتة

دالةٌ تطبّق خصم كبار العملاء بناءً على رصيدٍ من خدمةٍ خارجية. نُمرّر الخدمة معاملًا فنستبدلها في الاختبار (حقن الاعتماد):

main.py
def final_price(price, loyalty_service):    points = loyalty_service()        # اعتماد خارجيّ    return price * 0.9 if points >= 100 else price # في الإنتاج: loyalty_service يستعلم من قاعدة بيانات.# في الاختبار: stub يعيد قيمةً ثابتة — سريع وحتميّ.def stub_vip():   return 150      # عميل مميّزdef stub_new():   return 10       # عميل جديد print("مميّز:", final_price(200, stub_vip))   # خصم: 180print("جديد:", final_price(200, stub_new))    # بلا خصم: 200
Shell×
>>> %Run main.py
مميّز: 180.0
جديد: 200
>>>

الـ stub عزل الاختبار عن قاعدة البيانات تمامًا. نتحكّم في «النقاط» فنختبر فرعي الخصم بسهولةٍ وسرعة.

٣Mock: راقب كيف اُستُدعِي

أحيانًا المهمّ ليس القيمة المُعادة بل أن شيئًا حدث: هل أُرسِل بريد التأكيد؟ Mock يسجّل الاستدعاءات لنتحقّق منها. بايثون يقدّم unittest.mock:

main.py
from unittest.mock import Mock def checkout(cart_total, send_email):    if cart_total > 0:        send_email("تم الطلب")    return "مكتمل" # mock بديلٌ نراقبهnotifier = Mock()result = checkout(300, notifier) # نتحقّق من السلوك لا القيمةprint("النتيجة:", result)print("أُرسِل البريد؟", notifier.called)print("مرّات الإرسال:", notifier.call_count)notifier.assert_called_once_with("تم الطلب")   # يفشل إن لم يُستدعَ هكذاprint("✓ البريد أُرسِل مرّةً بالرسالة الصحيحة")
Shell×
>>> %Run main.py
النتيجة: مكتمل
أُرسِل البريد؟ True
مرّات الإرسال: 1
✓ البريد أُرسِل مرّةً بالرسالة الصحيحة
>>>

توقّع وتحقّق

البديل الذي يعيد قيمةً ثابتة:

البديل الذي نراقب كيف اُستُدعِي:

نحاكي الاعتمادات:

فائدة المحاكاة:

الخلاصة

تحدٍّ برمجي: راقب الاستدعاء

متقدم

الدالة process تستدعي logger حين يكون المبلغ سالبًا. استخدم Mock وتحقّق أنه اُستُدعِي مرّةً لمبلغٍ سالب، واطبع عدد مرّات الاستدعاء.

المطلوب:
  • مرّر Mock() كـ logger، ونفّذ process(-5, logger).
  • اطبع logger.call_count.

ابدأ من هذا الكود، واضغط ▶ شغّل هنا لتكتب حلّك وتجرّبه مباشرة:

main.py
from unittest.mock import Mockdef process(amount, logger):    if amount < 0:        logger("مبلغ سالب")    return abs(amount)# استخدم Mock ونفّذ process(-5, ...)، ثم اطبع عدد مرّات الاستدعاء
المطلوب×
1
💡 تلميح 1
logger = Mock() ثم process(-5, logger).
💡 تلميح 2
print(logger.call_count).

حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.

main.py
from unittest.mock import Mockdef process(amount, logger):    if amount < 0:        logger("مبلغ سالب")    return abs(amount)logger = Mock()process(-5, logger)print(logger.call_count)
انتقل إلى تقييم الدرساختيار من متعدد، وصح وخطأ، وأكمل الفراغ، وتتبّع الناتج.ابدأ التقييم ←

📝 معلم؟ أرسل ملاحظة للمؤلف عن هذا الدرس