في اللحظة التي قمت فيها بتشغيل اختبارات العقود (contract tests) الخاصة بـ Specmatic على نسختي "المنتهية" من تطبيق Zerodha المبني بـ MERN-stack، رصدت الأداة خمسة عيوب حقيقية لم تكتشفها فحوصاتي اليدوية أبداً. ومن بين 178 حالة اختبار تم إنشاؤها، تسببت مجموعة الاختبارات في تعطل الـ API بطرق كانت ستؤثر على المستخدمين الحقيقيين — مثل أنواع بيانات غير صالحة، وانهيارات عند إدخال بيانات اعتماد مشوهة، وفساد صامت للبيانات، وعمليات تسجيل غير متماثلة (non-idempotent)، وتغيير كاسر للعقد أوقف بوابة التكامل المستمر (CI gate) في مسارها.

كيف تسللت الأخطاء عبر الاختبار اليدوي

جمع المشروع بين خلفية (backend) مبنية بـ Node.js، وواجهة أمامية (front-end) بـ React، وتخزين MongoDB، وRazorpay للمدفوعات. قمت بمراجعة مسارات التسجيل والدفع يدوياً وبدا كل شيء يعمل بشكل صحيح. ومع ذلك، فإن الاختبار اليدوي يختبر فقط "المسار السعيد" (happy path): فهو يؤكد أن الكود يعمل عندما يتبع المستخدمون الخطوات المقصودة، لكنه لا يثبت قدرة الخدمة على الصمود أمام الطلبات المشوهة أو سلوك العميل غير المتوقع.

عندما وجهت Specmatic نحو الكود الحالي، كان "العقد" (contract) — وهو وصف صريح لشكل الطلب والاستجابة لكل نقطة نهاية (endpoint) — بمثابة المصدر الموثوق للحقيقة. بعد ذلك، قامت الأداة بإنشاء مصفوفة ضخمة من السيناريوهات الإيجابية والسلبية تلقائياً، والتي لم يكن ليخطر ببال مختبر بشري كتابة الكثير منها.

العيوب الخمسة التي تم اكتشافها

  • فجوات في التحقق من صحة المدخلات – قبل المسار /newOrder الأرقام العشرية والنصوص لحقل quantity رغم أن العقد يتطلب رقماً صحيحاً (integer). تسببت الاختبارات التي أرسلت أنواعاً خاطئة في جعل الـ API يتصرف بشكل غير صحيح.
  • أخطاء تسجيل دخول غير معالجة – أدى إرسال بيانات اعتماد مشوهة إلى مسار المصادقة (authentication route) إلى حدوث استثناء أثناء التشغيل (runtime exception) لأن الكود كان يفتقر إلى فحوصات النوع (type checks).
  • فساد صامت في عمليات الدفع – سمح المسار /verify-payment بقيم منطقية (boolean) لحقل amount. وعندما تسللت قيمة true عبر النظام، سجلت قاعدة البيانات عملية دفع ناجحة بقيمة صفر، مما أدى إلى تضخيم أرقام الإيرادات بصمت.
  • فقدان خاصية التماثل (idempotency) – فشلت عملية تشغيل مسار التسجيل للمرة الثانية في مجموعة الاختبارات، حيث حاول المسار إعادة إنشاء مستخدم موجود بالفعل بدلاً من التعامل مع التكرار بسلاسة.
  • اكتشاف تغيير كاسر للعقد – قمت بتغيير نوع بيانات في العقد عمداً، فرفضت خط أنابيب الـ CI التغيير فوراً، مما منع إصدار نسخة كاسرة للنظام.

لماذا تهم اختبارات العقود في مسارات التكامل المستمر (CI pipelines)

  • الاختبار السلبي على نطاق واسع – كانت معظم الحالات الـ 178 عبارة عن مدخلات لحالات حافة (edge-case). وكتابة هذه الحالات يدوياً ستستغرق وقتاً طويلاً للغاية.
  • الأمان للعملاء من الأطراف الثالثة – تحدد العقود ما تعد به الخدمة للمستهلكين الخارجيين. إذا انحرف التنفيذ عن العقد، يفشل اختبار العقد، مما يحمي التطبيقات التي تعتمد على هذه الخدمة.
  • حلقة تغذية راجعة سريعة – أوقفت بوابة الـ CI تغييراً كاسراً قبل دمجه، مما وفر على الفريق عملية تراجع (rollback) مكلفة.
  • تحسين جودة الكود – كان إضافة مسار actuator وجعل مسار التسجيل متماثلاً (idempotent) خطوات ضرورية لجعل العقد قابلاً للاختبار، مما أدى بدوره إلى تحصين الخدمة.

المقايضة التي يجب على المطورين موازنتها

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

ما يجب مراقبته مستقبلاً

  • اعتماد أوسع للتكامل المستمر (CI) – مع دمج المزيد من الفرق لمجموعات العقود في مساراتها، من المرجح أن تصبح الأدوات أسرع وأكثر قابلية للضبط.
  • تنسيقات عقود موحدة – قد تسهل المواصفات الناشئة مشاركة العقود عبر الخدمات والفرق المختلفة.
  • أتمتة تحديثات المواصفات – الأدوات التي تستنتج العقود من تغييرات الكود قد تقلل من عبء الصيانة اليدوية.

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

المستودع: https://github.com/priya3054/zerodha-specmatic