ماذا يحدث لرمزك قبل أن يصل إلى الإنتاج
لقد أنهيت للتو خاصية جديدة. جربتها محليًا، عملت بشكل جيد، ثم دفعت الكود. ماذا يحدث بعد ذلك؟
بين عملية الدفع ولحظة تشغيل الكود في الإنتاج، يحدث الكثير. ليس فقط بناء القطعة البرمجية (artifact)، بل التحقق مما إذا كان الكود آمنًا وصحيحًا وقابلًا للصيانة. إذا تخطيت هذه الفحوصات، فأنت تراهن على أن كل مطور في الفريق يكتب كودًا مثاليًا في كل مرة. لا أحد يفعل ذلك.
عملية تشغيل الفحوصات الآلية في كل مرة يدفع فيها شخص ما كودًا تسمى التكامل المستمر (Continuous Integration) أو CI. الفكرة بسيطة: اكتشاف المشاكل مبكرًا، عندما يكون إصلاحها رخيصًا. خطأ يُكتشف بعد خمس دقائق من الدفع يكلف فنجان قهوة. خطأ يُكتشف في الإنتاج يكلف تقرير حادث، واسترجاعًا للإصدار السابق، وسهرًا ليلة كاملة.
ابدأ باختبارات الوحدة
أول شيء تشغله معظم خطوط الأنابيب (pipelines) هو اختبارات الوحدة. لكن ما يُعتبر اختبار وحدة جيدًا أهم مما يدركه معظم الفرق.
اختبار الوحدة الجيد لا يختبر دالة أو طريقة واحدة فقط لأنها موجودة. إنه يختبر سلوكًا ذا معنى من نقطة دخول مناسبة. بالنسبة لخدمة خلفية (backend service)، نقطة الدخول هذه عادة ما تكون نقطة نهاية API (API endpoint) أو حالة استخدام (use case). الاختبار يستدعي تلك النقطة، ويتحقق مما إذا كانت الاستجابة تطابق ما تتوقعه. في الخلفية، يمر الطلب عبر المتحكم (controller)، طبقة الخدمة (service layer)، منطق المجال (domain logic)، وحدود المستودع (repository boundary). مسار التطبيق الداخلي يعمل بشكل حقيقي؛ الخدمات الخارجية مثل قواعد بيانات الإنتاج، قوائم انتظار الرسائل، وواجهات برمجة التطبيقات الخارجية تُستبدل بنماذج اختبارية محكومة (test doubles)، أو مثيلات اختبار محلية، أو كائنات وهمية (mocks)، أو نماذج جزئية (stubs).
لهذا النهج ميزة عملية: يمكنك تغيير التنفيذ الداخلي لدالة دون كسر الاختبار. الاختبار يهتم بما يفعله النظام، وليس كيف يفعله. هذا يعني أنه يمكنك إعادة الهيكلة (refactor) بحرية، طالما أن السلوك يبقى كما هو.
يجب أن تكون اختبارات الوحدة سريعة. إذا استغرقت أكثر من بضع ثوانٍ، فهناك شيء غير صحيح. ولأنها سريعة، يمكنك تشغيلها مع كل commit. هذا يعطي المطورين تغذية راجعة فورية: "تغييرك كسر شيئًا، أصلحه الآن".
التحليل الثابت (Linting) يكتشف الأسلوب والروائح الكودية
بعد اجتياز اختبارات الوحدة، يأتي الفحص التالي عادة وهو التحليل الثابت (linting). التحليل الثابت لا يختبر المنطق. إنه يتحقق مما إذا كان الكود يتبع قواعد أسلوب متسقة، وما إذا كانت هناك أنماط تؤدي غالبًا إلى أخطاء.
أداة التحليل الثابت ستكتشف أشياء مثل:
- متغير تم تعريفه ولكن لم يُستخدم أبدًا
- دالة طويلة جدًا ويجب تقسيمها
- مسافات بادئة أو تسمية غير متسقة
- أنماط قد تكون غير آمنة تبدو صحيحة ولكنها ليست كذلك
قد يبدو التحليل الثابت أمرًا ثانويًا مقارنة بالصحة، لكنه أهم مما تظن. الكود الذي يبدو متناسقًا أسهل في القراءة. الكود الأسهل في القراءة أسهل في المراجعة. الكود الأسهل في المراجعة يحتوي على أخطاء أقل. إنها سلسلة تبدأ بفحص آلي بسيط.
اختبارات التكامل تتحقق من الاتصالات
اختبارات الوحدة تثبت أن سلوك التطبيق يعمل بينما الجيران الخارجيون تحت السيطرة. اختبارات التكامل تثبت أن التطبيق يمكنه العمل مع التبعيات الحقيقية.
بالنسبة لخدمة خلفية، قد يقوم اختبار التكامل بتشغيل قاعدة بيانات اختبار حقيقية، وتشغيل الخدمة، والتحقق من أن استدعاء API يقرأ ويكتب البيانات بشكل صحيح. أو قد يختبر أن عاملًا خلفيًا (background worker) يمكنه إرسال رسالة إلى قائمة انتظار ومعالجة الرد.
اختبارات التكامل أبطأ من اختبارات الوحدة. إنها تحتاج إلى تبعيات حقيقية: قاعدة بيانات، قائمة انتظار، ربما مخبأ (cache). لهذا السبب تعمل بعد اختبارات الوحدة، وليس قبلها. إذا فشل اختبار وحدة، فلا فائدة من تشغيل اختبارات التكامل. التغيير معطل بالفعل.
بعض الفرق تشغل اختبارات التكامل ضد بيئة اختبار مخصصة تحاكي الإنتاج بأكبر قدر ممكن. آخرون يشغلونها داخل خط أنابيب CI باستخدام الحاويات (containers). في كلتا الحالتين، الهدف هو نفسه: اكتشاف المشاكل التي تظهر فقط عندما تتفاعل المكونات.
فحوصات الأمان تبحث عن الثغرات في الكود الخاص بك
فحص الأمان يتحقق من الكود الذي كتبته بحثًا عن الثغرات الشائعة. أشياء مثل حقن SQL (SQL injection)، البرمجة عبر المواقع (cross-site scripting)، أو استخدام دوال التشفير بشكل غير صحيح.
هذه الفحوصات هي أدوات آلية تبحث عن أنماط معروفة بأنها خطيرة. إنها ليست مثالية. قد تفوتها أشياء، وقد تنتج نتائج إيجابية خاطئة (false positives). لكنها تكتشف الثمار المنخفضة التي قد يغفل عنها المراجعون البشريون.
ثغرة تُكتشف أثناء CI تكلف تذكرة وإصلاحًا. ثغرة تُكتشف في الإنتاج تكلف اختراقًا، وإفشاءً، والكثير من الثقة.
فحوصات التبعيات تبحث عن الثغرات في المكتبات الخاصة بك
تقريبًا لا توجد خدمة خلفية مكتوبة من الصفر. أنت تستخدم أطر عمل (frameworks)، مكتبات (libraries)، وحزم (packages). كل واحدة منها هي كود كتبه شخص آخر، وهذا الكود قد يحتوي على ثغرات.
فحص التبعيات يقارن إصدارات مكتباتك بقواعد بيانات الثغرات العامة. إذا كانت المكتبة التي تستخدمها تحتوي على مشكلة أمنية معروفة، فإن الفحص يشير إليها. بعض الفرق تُكوّن خط الأنابيب لمنع البناء (build) إذا تم العثور على ثغرة حرجة. آخرون يتركونها تمر ولكن يخطرون الفريق للمراجعة اليدوية.
هذا الفحص مهم لأن الثغرات في التبعيات شائعة وغالبًا ما تكون خطيرة. حادثة Log4j في عام 2021 هي مثال معروف: مكتبة تسجيل مستخدمة على نطاق واسع كانت تحتوي على ثغرة سمحت بتنفيذ كود عن بُعد. الفرق التي كان لديها فحص تبعيات في خط أنابيبها اكتشفت ذلك بسرعة. الفرق التي لم يكن لديها أمضت أيامًا في معرفة ما تستخدمه.
ترتيب الفحوصات مهم
تسلسل هذه الفحوصات ليس عشوائيًا. إنه يتبع منطقًا عمليًا:
- اختبارات الوحدة والتحليل الثابت تعمل أولاً لأنها سريعة. إنها تصفي التغييرات المكسورة أو غير المرتبة بشكل واضح فورًا.
- اختبارات التكامل تعمل بعد ذلك. إنها أبطأ ولكنها لا تزال مهمة. إذا نجحت اختبارات الوحدة ولكن فشلت اختبارات التكامل، فأنت تعلم أن المكونات لا تعمل معًا.
- فحوصات الأمان وفحوصات التبعيات تعمل أخيرًا. غالبًا ما تكون الأبطأ، ومن غير المرجح أن تفشل في تغيير نموذجي.
هذا الترتيب يعطي المطورين تغذية راجعة سريعة للمشاكل الشائعة. إذا كسرت اختبار وحدة، ستعرف في غضون ثوانٍ. إذا أدخلت تبعية ضعيفة، ستكتشف ذلك في غضون دقائق، وليس أيامًا.
ليس كل فحص يجب أن يمنع خط الأنابيب
بعض الفرق تشغل جميع الفحوصات كمانعة (blocking): إذا فشل أي فحص، يتوقف خط الأنابيب، ولا يتم بناء القطعة البرمجية. فرق أخرى تسمح لبعض الفحوصات بالمرور مع تحذيرات، خاصة فحوصات الأمان التي قد تنتج نتائج إيجابية خاطئة.
الشيء المهم هو الاتساق. كل تغيير يمر عبر نفس الفحوصات. الجودة لا تعتمد على ما إذا كان المطور يتذكر تشغيل الاختبارات محليًا. إنها تعتمد على خط الأنابيب.
قائمة مراجعة عملية لخط أنابيب CI الخاص بك
إذا كنت تقوم بإعداد أو مراجعة خط أنابيب CI لخدمة خلفية، إليك قائمة مراجعة قصيرة:
- اختبارات الوحدة تعمل مع كل commit وتكتمل في أقل من دقيقة
- التحليل الثابت يعمل قبل أو بجانب اختبارات الوحدة
- اختبارات التكامل تعمل ضد تبعيات حقيقية في بيئة محكومة
- فحص الأمان يتحقق من الكود الخاص بك بحثًا عن الثغرات الشائعة
- فحص التبعيات يقارن مكتباتك بقواعد بيانات الثغرات المعروفة
- خط الأنابيب يفشل بسرعة: الفحوصات السريعة تعمل أولاً، والفحوصات الأبطأ تعمل لاحقًا
- كل تغيير يمر عبر نفس الفحوصات، بغض النظر عن من كتبه
ماذا بعد
بمجرد اجتياز جميع هذه الفحوصات، يتم بناء القطعة البرمجية وتكون جاهزة. لكن الحصول على القطعة البرمجية المبنية هو نصف القصة فقط. السؤال التالي هو كيفية وضع هذا الإصدار الجديد على الخوادم دون تعطيل المستخدمين. هذا هو المكان الذي تأتي فيه استراتيجيات النشر (deployment strategies)، وهذا ما سنغطيه في المرة القادمة.
في الوقت الحالي، الخلاصة هي: جودة نظام الإنتاج الخاص بك تُحدد قبل وقت طويل من وصوله إلى الإنتاج. تُحدد في الدقائق التي تلي كل عملية دفع، عندما تقرر الفحوصات الآلية ما إذا كان الكود الخاص بك آمنًا للمتابعة. اجعل تلك الفحوصات سريعة، واجعلها متسقة، واجعلها تعمل في كل مرة.