اختبار العقود: اكتشاف خروقات واجهات API قبل الوصول إلى الإنتاج

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

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

المشكلة الحقيقية التي تفوتها اختبارات التكامل

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

فكر في الأمر بهذه الطريقة: الفريق أ يدير خدمة المستخدمين. الفريق ب يدير خدمة الإشعارات التي تستهلك بيانات المستخدمين. يقرر الفريق أ تنظيف استجابة API عن طريق إزالة حقل يبدو غير مستخدم داخل قاعدة الشيفرة الخاصة بهم. اختبارات التكامل الخاصة بهم تنجح لأنهم يختبرون مقابل خدمتهم الخاصة. اختبارات التكامل الخاصة بالفريق ب تنجح أيضًا لأنهم يختبرون مقابل نموذج mock أو لقطة من API القديم. العقد المكسور لا يكشف عن نفسه إلا عندما تعمل كلتا الخدمتين معًا في بيئة الاختبار أو الإنتاج.

ما يفعله اختبار العقود فعليًا

يجعل اختبار العقود الاتفاق الضمني بين الخدمات صريحًا وقابلاً للفحص. في كل مرة تتواصل فيها خدمتان من خلال API، هناك اتفاق حول ما يتم إرساله وما يتم استقباله. يحول اختبار العقود هذا الاتفاق إلى فحوصات آلية.

يعمل المفهوم مع دورين:

يُظهر مخطط التسلسل التالي كيف ينشر المزوّد عقدًا وكيف يتحقق المستهلك ضده قبل النشر:

sequenceDiagram participant Provider as User Service (Provider) participant Contract as Contract Repository participant Consumer as Notification Service (Consumer) Provider->>Contract: Publish API contract Consumer->>Contract: Fetch contract Consumer->>Consumer: Verify response matches contract alt Contract valid Consumer->>Consumer: Deploy safely else Contract broken Consumer->>Consumer: Fail build, alert team end
  • المزوّد (Provider): الخدمة التي تقدم API
  • المستهلك (Consumer): الخدمة التي تستدعي API

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

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

لماذا اختبارات العقود أسرع من اختبارات التكامل

الميزة العملية الأكبر هي السرعة والاستقلالية. لا تحتاج اختبارات العقود إلى تشغيل خدمات أخرى. لا تحتاج إلى قاعدة بيانات حقيقية أو بيئة اختبار كاملة. تقوم فقط بتشغيل المزوّد مع بيانات اختبار محددة مسبقًا والتحقق مما إذا كانت الاستجابة تطابق العقد.

يكتمل اختبار العقد في ثوانٍ. يستغرق اختبار التكامل الذي يشغل خدمات متعددة وقواعد بيانات دقائق. في خط أنابيب CI، هذا الفرق مهم. يمكنك تشغيل اختبارات العقود مبكرًا والفشل بسرعة، دون انتظار مجموعات التكامل باهظة الثمن.

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

ما لا تغطيه اختبارات العقود

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

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

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

ضع اختبارات العقود بعد اختبارات الوحدة وقبل اختبارات التكامل. هذا الترتيب منطقي لأن اختبارات العقود أسرع من اختبارات التكامل ولكنها توفر ثقة إضافية بأن الخدمات التي ستختبرها معًا لا تزال متوافقة. إذا فشل اختبار العقد، فلا فائدة من تشغيل اختبارات التكامل باهظة الثمن التي من المحتمل أن تفشل أيضًا.

إليك ترتيب خط أنابيب نموذجي:

  1. اختبارات الوحدة
  2. اختبارات العقود
  3. اختبارات التكامل
  4. اختبارات النهاية إلى النهاية (إذا لزم الأمر)

من أين تبدأ

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

ابحث عن هذه الإشارات:

  • الخدمات التي يكسر فيها تغيير فريق واحد ميزة فريق آخر بشكل متكرر
  • واجهات API التي يعتمد عليها عدة مستهلكين
  • الخدمات التي تغير تنسيق استجابتها بانتظام
  • واجهات API الخارجية التي تغيرت بشكل غير متوقع في الماضي

ركز على تلك أولاً. بمجرد أن تعمل اختبارات العقود وتكتشف مشكلات حقيقية، قم بالتوسع إلى خدمات أخرى تدريجيًا.

قائمة مراجعة عملية قبل إضافة اختبارات العقود

  • حدد أفضل ثلاثة أزواج من الخدمات التي تسبب أكبر قدر من الكسر عبر الفرق
  • قرر ما إذا كنت ستستخدم عقودًا يحركها المستهلك أم يحركها المزوّد
  • اختر أداة اختبار عقود تناسب مجموعتك التقنية (Pact، Spring Cloud Contract، أو ما شابه)
  • ابدأ بمزوّد واحد ومستهلك واحد
  • قم بتشغيل اختبارات العقود في CI قبل اختبارات التكامل
  • قم بإعداد عملية لإخطار الفرق عند كسر عقد

الخلاصة

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