تسبب نشر (deployment) أخير في تعطل ثلاث خدمات مصغرة (microservices) رغم اجتياز جميع اختبارات الوحدة (unit tests) واختبارات التكامل (integration tests) وفحوصات الخادم الوهمي (mock-server). استبدل الفريق نماذج الـ API الوهمية (API mocks) باختبار العقود (contract testing). وفي غضون ستة أشهر، ارتفع عدد العقود من ثلاثة إلى 47، وانخفض معدل فشل التكامل الشهري من حادثتين إلى صفر.

لماذا لا يمكن للنماذج الوهمية (mocks) حمايتك

يقوم الخادم الوهمي (mock server) بمحاكاة الشكل الذي يتوقعه المستهلك فقط؛ لكنه لا يتحقق أبداً مما إذا كان المزود (provider) يقدم ذلك الشكل بالفعل. إذا قام المزود بتغيير اسم حقل ما — لنقل من name إلى display_name — فسيظل النموذج الوهمي يعيد البيانات القديمة، وستظل اختبارات المستهلك ناجحة (green)، بينما ينهار النظام الفعلي. كان الفشل في بيئة الإنتاج (production) في تمام الساعة 2 ظهراً يوم الثلاثاء هو بالضبط هذا: لقد "كذب" النموذج الوهمي بشأن العقد الحقيقي.

اختبار العقود يسد الفجوة

يجبر اختبار العقود طرفي الـ API على الاتفاق على تعريف مشترك قبل أن يلمس أي كود بيئة الإنتاج. هناك نهجان شائعان:

  • العقود المدفوعة بالمستهلك (Consumer-driven contracts) – حيث تكتب الخدمة المستهلكة التوقعات، وتقوم الخدمة المزودة بالتحقق من صحتها. هذا النهج يعمل بشكل جيد مع الخدمات المصغرة الداخلية التي تتطور معاً.
  • العقود المدفوعة بالمزود (Provider-driven contracts) – حيث ينشر المزود مواصفات (specification)، ويقوم المستهلكون بمطابقة كودهم معها. هذا هو النمط المعتاد لواجهات برمجة التطبيقات (APIs) العامة.

يمنع النهج الأول بشكل عام حدوث أعطال في التكامل داخل بنية الخدمات المصغرة.

كيف يعمل العقد المدفوع بالمستهلك

  1. يكتب المستهلك اختباراً يصف بدقة ما يحتاجه من المزود.
  2. يؤدي تشغيل الاختبار إلى إنشاء ملف pact – وهو مستند JSON يسجل تلك التوقعات.
  3. يقوم المزود بتشغيل خدمته الحقيقية مقابل ملف الـ pact في خط أنابيب التكامل المستمر (CI pipeline) الخاص به.
  4. إذا قام المزود بتغيير حقل ما، تفشل عملية التحقق ويتم إيقاف عملية البناء (build).

ولأن عملية التحقق تجرى على كود المزود الفعلي، يتم اكتشاف أي تغيير مكسر (breaking change) مبكراً، وليس بعد النشر.

أين تقع اختبارات العقود في هرم الاختبار الخاص بك

  • اختبارات الوحدة (Unit tests) – سريعة، وتختبر المنطق المعزول.
  • اختبارات العقود (Contract tests) – متوسطة السرعة، وتؤكد التزام اتفاقيات الـ API.
  • الاختبارات الشاملة (End-to-end tests) – بطيئة، وتختبر تدفقات العمل الكاملة.

تعامل مع اختبارات العقود كجسر بين التغذية الراجعة السريعة لاختبارات الوحدة والتغطية الواسعة لمجموعات الاختبارات الشاملة. استهدف نقاط التكامل التي تتعطل غالباً وابدأ بنقطتي نهاية (endpoints) أو ثلاث نقاط حرجة.

قصة تطبيق من الواقع

بدأ الفريق الذي ألهم هذا المقال بثلاثة عقود تغطي أكثر استدعاءاتهم هشاشة. وبعد ستة أشهر، أصبح لديهم 47 عقداً تغطي غالبية حركة البيانات بين الخدمات. وخلال تلك الفترة، انخفضت حوادث تعطل الـ API من حادثتين شهرياً إلى صفر.

متى قد لا يستحق اختبار العقود العناء

  • إذا كنت مطوراً منفرداً تحتفظ بجميع الخدمات في مستودع (repository) واحد.
  • إذا كانت الـ API مستقرة للغاية ولم تتغير منذ سنوات.
  • إذا كنت تبني نموذجاً أولياً مؤقتاً سيتم التخلص منه قريباً.

في هذه السيناريوهات، قد تفوق تكلفة صيانة العقود الفائدة المرجوة منها.

السلبيات المحتملة وكيفية التخفيف منها

  • حافظ على إصدارات العقود (versioned) جنباً إلى جنب مع الكود الذي تصفه.
  • قم بأتمتة عملية التحقق في كل تشغيل لـ CI لتجنب العقود القديمة (stale).
  • راجع تغييرات العقود في طلبات السحب (pull requests) لاكتشاف أي تعطل غير مقصود.

الخلاصة

إذا كنت لا تزال تعتمد على نماذج وهمية (mocks) مصممة يدوياً لتقنع نفسك بأن خدماتك يمكنها التواصل، فأنت تراهن على وعد زائف. يحول اختبار العقود هذا الرهان إلى اتفاق قابل للتحقق، مما يكتشف التغييرات المكسرة قبل وصولها إلى بيئة الإنتاج، وكما تظهر أرقام الفريق، يمكنه القضاء على فشل التكامل تماماً.