أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالعروض
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

المنصة مبنية على الوضوح، التطبيق، والنتيجة النافعة: شرح مرتب يساعدك تفهم الأدوات، تكتب كودًا أفضل، وتستخدم الذكاء الاصطناعي بوعي داخل العمل الحقيقي.

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالعروضالمدونةالدخول
البرمجة بالعربي

Dependency Injection في Python: اختبر الدفع بدون Stripe حقيقي

📅 ٢٦ أبريل ٢٠٢٦⏱ 4 دقائق قراءة
Dependency Injection في Python: اختبر الدفع بدون Stripe حقيقي

Dependency Injection في Python: اختبر الدفع بدون Stripe حقيقي

مستوى القارئ: متوسط

لو اختبار الدفع عندك بيستنى Stripe sandbox، المقال ده هيخليك تفصل منطق الدفع عن الخدمة الخارجية وتحوّل الاختبار من ثواني غير مستقرة إلى عشرات المللي ثانية.

المشكلة باختصار

الطريقة الشائعة إنك تكتب كلاس CheckoutService جوّاه إنشاء مباشر لـ StripeClient. الكود بيشتغل، لكن الاختبار بيتحوّل من اختبار منطق إلى اختبار شبكة، credentials، rate limits، وحالة خدمة خارجية.

في سيناريو واقعي، لو عندك 120 اختبار وحدة في CI، وكل اختبار دفع يلمس API خارجي ويأخذ 1.5 إلى 2 ثانية، هتضيف دقيقتين أو أكثر لكل pull request. الأسوأ إن فشل الشبكة يظهر لك كأنه bug في الكود. ركز: المشكلة مش في Stripe. المشكلة إن الكود مربوط بتفصيلة تنفيذية من أول يوم.

مخطط يوضح فصل Checkout Handler عن StripeClient عبر PaymentPort واستخدام FakePayment في الاختبارات

المثال الأول: كود مربوط بالخدمة مباشرة

خلينا نمسك مثال دفع بسيط. المطلوب إن الطلب لا يكتمل إلا لو الدفع نجح. النسخة السريعة غالبًا تبقى بالشكل ده:

Python
class StripeClient:
    def charge(self, user_id: str, amount_cents: int) -> str:
        # هنا في الحقيقة هيبقى فيه HTTP request
        return "stripe_charge_123"

class CheckoutService:
    def checkout(self, user_id: str, amount_cents: int) -> dict:
        client = StripeClient()
        charge_id = client.charge(user_id, amount_cents)
        return {"status": "paid", "charge_id": charge_id}

الكود واضح، لكن الاختبار صعب. لو عايز تختبر حالة فشل الدفع، لازم تعمل monkeypatch أو تضرب sandbox أو تستخدم mock على مسار داخلي هش. أي تغيير في اسم الكلاس أو مكانه يكسر الاختبار حتى لو السلوك العام كما هو.

الحل: اعتمد على السلوك بدل الكلاس

Dependency Injection معناها ببساطة إن الكود يستقبل الاعتماد من الخارج بدل ما يصنعه داخله. التعريف الأدق: نقل مسؤولية إنشاء dependency إلى طبقة أعلى، بحيث يعتمد الكود على interface أو protocol يصف السلوك المطلوب.

مثال ممتع وبسيط: بدل ما الطباخ يبني الفرن بنفسه في كل طلب، هو يستقبل أي فرن يقدر ينفّذ وظيفة bake. في الإنتاج تستخدم فرن حقيقي. في الاختبار تستخدم فرن تدريبي لا يستهلك غاز ولا ينتظر حرارة. نفس الفكرة بالظبط في الدفع.

Python
from typing import Protocol

class PaymentPort(Protocol):
    def charge(self, user_id: str, amount_cents: int) -> str:
        ...

class StripePayment:
    def charge(self, user_id: str, amount_cents: int) -> str:
        # production HTTP call to Stripe API
        return "stripe_charge_123"

class CheckoutService:
    def __init__(self, payment: PaymentPort):
        self.payment = payment

    def checkout(self, user_id: str, amount_cents: int) -> dict:
        if amount_cents <= 0:
            raise ValueError("amount must be positive")

        charge_id = self.payment.charge(user_id, amount_cents)
        return {"status": "paid", "charge_id": charge_id}

هنا CheckoutService لا يعرف Stripe. هو يعرف فقط إن فيه dependency عندها دالة charge. ده يخلي الاختبار أسرع وأوضح.

اختبار سريع بدون شبكة

الاختبار التالي يستخدم fake بسيط. مش mock غامض، ومش API خارجي. كلاس صغير يعبّر عن السلوك المطلوب:

Python
class FakePayment:
    def __init__(self):
        self.calls = []

    def charge(self, user_id: str, amount_cents: int) -> str:
        self.calls.append((user_id, amount_cents))
        return "fake_charge_001"

def test_checkout_charges_user():
    payment = FakePayment()
    service = CheckoutService(payment)

    result = service.checkout("user_42", 2500)

    assert result == {"status": "paid", "charge_id": "fake_charge_001"}
    assert payment.calls == [("user_42", 2500)]

def test_checkout_rejects_zero_amount():
    payment = FakePayment()
    service = CheckoutService(payment)

    try:
        service.checkout("user_42", 0)
    except ValueError as exc:
        assert str(exc) == "amount must be positive"
    else:
        raise AssertionError("expected ValueError")

على جهاز عادي، اختبار زي ده ممكن يخلص في 30 إلى 60ms. نفس الاختبار لو ضرب شبكة خارجية قد يأخذ 1500 إلى 2000ms، وأحيانًا يفشل بسبب timeout. الرقم هنا تقديري، لكن الفارق العملي واضح: أنت تقيس منطقك بدل ما تقيس الإنترنت.

رسم يوضح تقليل زمن اختبار الدفع من 1800ms مع API حقيقي إلى 42ms مع FakePayment

الـ trade-off هنا

المكسب: اختبارات أسرع، كود أقل هشاشة، وقدرة أسهل على تبديل Stripe بـ PayPal أو مزود داخلي لاحقًا. بتكسب كمان إن منطقك يبقى قابل للاختبار بدون secrets وبدون sandbox.

الثمن: هتضيف طبقة تصميم زيادة. في ملف صغير أو سكربت مرة واحدة، الطبقة دي ممكن تكون overengineering. كمان الفيك الخاطئ ممكن يكذب عليك؛ لو FakePayment لا يشبه سلوك مزود الدفع الحقيقي، الاختبار هيمر بينما الإنتاج يفشل. أفضل طريقة هي توازن واضح: unit tests تستخدم fake، وintegration tests قليلة تضرب Stripe sandbox مرة أو مرتين في pipeline منفصل.

متى لا تستخدم هذه الطريقة

لا تستخدم Dependency Injection بشكل ثقيل لو عندك سكربت 50 سطر بيتشغل يدويًا مرة في الشهر. ولا تبدأ بحاوية DI ضخمة في مشروع Python صغير. الافتراض إن عندك منطق يتغير، اختبارات متكررة، أو خدمة خارجية تسبب بطء وفشل متقطع. لو الاعتماد ثابت وبسيط، مرّر function عادية بدل ما تبني abstraction كامل.

مصادر اعتمد عليها المقال

  • Python docs: typing.Protocol
  • pytest docs: fixtures
  • Stripe docs: testing
  • Martin Fowler: Inversion of Control Containers and Dependency Injection

الخطوة التالية

افتح كلاس واحد عندك بيعمل HTTP request داخله، وغيّر الكونستركتور بحيث يستقبل dependency من الخارج. بعد كده اكتب fake من 10 أسطر واختبر حالة نجاح وحالة فشل قبل ما تلمس أي mock framework.

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة