AzLearn

Mocks و Fixtures

Mocks & Fixtures

مفهوم ~22 دقيقة

Mocks و Fixtures — Mocks & Fixtures

كودك في الواقع لا يعيش وحيداً. يتصل بقواعد بيانات، يُرسل طلبات HTTP، يقرأ ملفات، يستخدم ساعة النظام. هذه التبعيات الخارجية (External Dependencies) تُصعّب الاختبار: الشبكة قد تكون بطيئة أو غير متاحة، قاعدة البيانات تحتاج إعداداً، والوقت الحقيقي يُصعّل اختبار الكود الزمني.

الحل: الاستبدال المؤقت (Mocking). في وقت الاختبار، تستبدل الجزء الخارجي بكائن تحكم فيه أنت — يتصرف كما تريد ويُخبرك كيف استُدعي.

ما هو Mock؟

Mock هو كائن يُحاكي سلوك كائن حقيقي. يمكنه:

  • إرجاع قيمة مُحددة عند استدعائه
  • رفع استثناء عند استدعائه
  • تسجيل كيف استُدعي (عدد المرات، بأي معاملات)

المكتبة المعيارية توفّر unittest.mock — لا تثبيت مطلوب.

MagicMock — الكائن الذكي

MagicMock هو الأداة الرئيسية. يقبل أي استدعاء ويُعيد MagicMock آخر — يمكنك برمجة ما يُرجعه:

main.go

كيف يُساعد Mock في الاختبار

تخيّل دالة تحسب إجمالي الطلب وتُرسل إشعاراً بالبريد الإلكتروني. عند الاختبار، لا نريد إرسال بريد حقيقي — نريد فقط التحقق من أن الدالة طلبت إرسال البريد بالمعاملات الصحيحة.

main.go

patch — استبدال التبعيات المدمجة

أحياناً لا تستطيع تمرير التبعية كمعامل — الكود يستورد الوحدة مباشرة. patch يستبدل الاسم مؤقتاً في وقت الاختبار:

# الكود الأصلي — original code
import requests

def get_user_data(user_id):
    response = requests.get(f"https://api.example.com/users/{user_id}")
    return response.json()

في الاختبار نستبدل requests.get بـ mock:

main.go

side_effect — سلوك ديناميكي

بدل return_value الثابت، side_effect يُشغّل دالة أو يرفع استثناء:

main.go

Fixtures في pytest — مراجعة مع مثال كامل

في pytest، الـ fixture يُنشئ مورداً ويُمرّره للاختبار تلقائياً. المثال التالي يوضح كيفية تنفيذه باستخدام unittest.setUp الذي يؤدي نفس الدور:

# أسلوب pytest — pytest style
@pytest.fixture
def user_service():
    service = UserService(db=FakeDatabase())
    yield service
    service.cleanup()

def test_create_user(user_service):
    user = user_service.create("أحمد", "[email protected]")
    assert user.id is not None

المكافئ بـunittest:

# أسلوب unittest — unittest style
class TestUserService(unittest.TestCase):
    def setUp(self):
        self.service = UserService(db=FakeDatabase())

    def tearDown(self):
        self.service.cleanup()

    def test_create_user(self):
        user = self.service.create("أحمد", "[email protected]")
        self.assertIsNotNone(user.id)

متى تستخدم Mock ومتى لا؟

استخدم Mock عندما:

  • الكود يتصل بشبكة أو قاعدة بيانات خارجية
  • التبعية بطيئة (API خارجي)
  • تريد محاكاة حالات خطأ صعبة (انقطاع الشبكة)
  • التبعية تُغيّر حالة خارجية (إرسال بريد، SMS)
  • الكود يعتمد على الوقت الحالي (datetime.now())

لا تستخدم Mock عندما:

  • التبعية بسيطة ومتوفرة (دالة رياضية، معالجة نصوص)
  • Mock يُصعّل الاختبار أكثر مما يُبسّطه
  • تريد اختبار التكامل الفعلي بين المكونات
# لا داعي لـ Mock هنا — No need for mock
def test_add():
    assert add(2, 3) == 5  # دالة بسيطة لا تبعيات

# Mock منطقي هنا — Mock makes sense
def test_send_order_notification():
    mock_email = MagicMock()
    # نختبر منطق الإشعار دون إرسال بريد حقيقي
تحدي — Challenge