يا جماعة الخير، السلام عليكم ورحمة الله.
اسمحوا لي آخذكم معي في رحلة بالزمن، ليلة خميس من ليالي عام 2018. كنا في الفريق على وشك إطلاق تحديث “بسيط” لتطبيق مهم لأحد عملائنا الكبار. التحديث كان عبارة عن تعديل صغير في نظام الفواتير، شيء لا يستدعي القلق نظرياً. ضغطنا زر النشر (Deploy) الساعة 11 مساءً، وتبادلنا نظرات الرضا والتعب، وبدأنا نلملم أغراضنا لنعود إلى بيوتنا.
لم تكد تمر ساعة حتى بدأ هاتف مدير المشروع بالرنين كجرس إنذار حريق. على الطرف الآخر، كان العميل يصرخ تقريباً: “نظام تسجيل الدخول لا يعمل! لا أحد من مستخدمينا يستطيع الدخول إلى حسابه!”.
تجمّد الدم في عروقنا. كيف يمكن لتعديل في الفواتير أن يكسر نظام تسجيل الدخول؟ لا علاقة بينهما على الإطلاق! عدنا مسرعين إلى أجهزتنا، وشعرنا بذلك الشعور المألوف الذي يغصّ في الحلق، شعور “ماذا كسرنا هذه المرة؟”. كانت ليلة طويلة من البحث والتنقيب، واكتشفنا في النهاية أن مكتبة مشتركة (shared library) قمنا بتحديثها كجزء من تعديل الفواتير، كانت تحتوي على تغيير “غير موثق” أثّر على طريقة التعامل مع جلسات المستخدمين (User Sessions).
في تلك الليلة، ونحن جالسون في الرابعة فجراً، محاطين بأكواب القهوة الفارغة، نظر إلينا قائد الفريق وقال جملة لن أنساها: “يا جماعة، هذا الوضع لا يمكن أن يستمر. كل تحديث نطلقه صار مثل لعب الروليت الروسية. يجب أن نجد طريقة أفضل.”
تلك الليلة كانت نقطة التحول. كانت بداية رحلتنا مع ما يُعرف بـ “الاختبار التراجعي الآلي” (Automated Regression Testing).
ما هو الاختبار التراجعي (Regression Testing)؟ وليش هو مهم؟
ببساطة، تخيل أن تطبيقك عبارة عن بناء معقد من قطع الليغو أو الجينغا. كلما أضفت ميزة جديدة (قطعة ليغو جديدة)، فأنت تخاطر بزعزعة استقرار البناء بأكمله، وقد تسقط قطعة أخرى في مكان بعيد تماماً لم تكن تتوقعه.
الاختبار التراجعي هو عملية التأكد من أن الميزات القديمة التي كانت تعمل بشكل صحيح لا تزال تعمل كذلك بعد إضافة ميزات جديدة أو إجراء تعديلات على الكود.
الهدف هو الإجابة على سؤال واحد حاسم قبل كل عملية نشر: “هل تسبب التغيير الجديد في كسر أي شيء كان يعمل من قبل؟”.
الجحيم اليدوي مقابل النعيم الآلي
في البداية، كنا نحاول القيام بهذا يدوياً. قبل كل إصدار، كنا نفتح جدول بيانات (Excel sheet) ضخم يحتوي على قائمة طويلة من الحالات الاختبارية:
- هل يمكن للمستخدم تسجيل الدخول؟ (نعم/لا)
- هل يمكن للمستخدم إعادة تعيين كلمة المرور؟ (نعم/لا)
- هل تظهر المنتجات في الصفحة الرئيسية؟ (نعم/لا)
- … وهكذا لمئات الحالات الأخرى.
كانت هذه العملية كابوساً حقيقياً. كانت تستغرق أياماً، مملة للغاية، وعرضة للخطأ البشري. أحياناً ينسى المختبِر خطوة، أو يختبر على بيئة خاطئة. والأسوأ من ذلك، مع نمو التطبيق، كانت القائمة تطول وتطول، وأصبحت العملية شبه مستحيلة.
وهنا يأتي دور الأتمتة (Automation). فبدلاً من أن يقوم إنسان بالنقر على الأزرار وملء الحقول مراراً وتكراراً، نكتب “نصوصاً برمجية” (Scripts) تقوم بذلك بالنيابة عنا، وبسرعة ودقة فائقتين.
كيف بدأنا رحلتنا العملية مع الأتمتة؟ (الدليل العملي)
الانتقال إلى الأتمتة لم يحدث بين عشية وضحاها. لقد كان عملية تدريجية، تعلمّنا فيها الكثير من الدروس. إليكم الخطوات التي اتبعناها، والتي أنصح أي فريق باتباعها.
الخطوة الأولى: اختيار الأدوات المناسبة (Choosing the Right Tools)
لا يوجد أداة واحدة تناسب الجميع. يعتمد الاختيار على تقنيات مشروعك. في حالتنا، كان لدينا تطبيق ويب، فكانت الخيارات الرئيسية:
- Selenium: الأب الروحي لأتمتة المتصفحات. قوي جداً ويدعم لغات برمجة متعددة، ولكنه قد يكون معقداً بعض الشيء في الإعداد.
- Cypress: أداة حديثة، سهلة الإعداد والكتابة، وموجهة بشكل أساسي للمطورين. توفر تجربة تفاعلية ممتازة أثناء كتابة الاختبارات.
- Playwright: من تطوير مايكروسوفت، وهي منافس قوي جداً لـ Cypress، وتتميز بدعمها لعدة متصفحات (Chrome, Firefox, Safari) بشكل أصلي وسرعتها العالية.
قررنا في النهاGية البدء بـ Cypress لسهولة استخدامه وسرعة تحقيق نتائج ملموسة، مما ساعد على رفع معنويات الفريق في البداية.
الخطوة الثانية: تحديد المسارات الحرجة (Identifying Critical Paths)
هذه نصيحة من ذهب: لا تحاول أتمتة كل شيء دفعة واحدة! ستحرق طاقتك ووقتك وستفشل. ابدأ بـ “المسارات الحرجة”، وهي الوظائف الأساسية التي إذا تعطلت، أصبح التطبيق عديم الفائدة.
في حالتنا، كانت هذه المسارات هي:
- عملية تسجيل مستخدم جديد.
- عملية تسجيل الدخول لمستخدم حالي.
- عملية تصفح المنتجات وإضافتها إلى السلة.
- عملية إتمام الشراء (Checkout).
ركّزنا كل جهودنا في البداية على كتابة اختبارات آلية تغطي هذه العمليات الأربع فقط. النجاح في هذه المرحلة أعطانا “شبكة أمان” أساسية.
الخطوة الثالثة: كتابة أول اختبار (والاحتفال به!)
لنجعل الأمر عملياً. هذا مثال بسيط جداً لكيف يبدو اختبار تسجيل الدخول باستخدام صيغة تشبه Cypress:
// tests/e2e/login.spec.js
describe('Login Functionality', () => {
it('should allow a registered user to log in successfully', () => {
// 1. زيارة صفحة تسجيل الدخول
cy.visit('/login');
// 2. العثور على حقل اسم المستخدم وكتابة الإيميل
cy.get('input[name="email"]').type('testuser@example.com');
// 3. العثور على حقل كلمة المرور وكتابتها
cy.get('input[name="password"]').type('StrongPassword123');
// 4. الضغط على زر تسجيل الدخول
cy.get('button[type="submit"]').click();
// 5. التحقق من أننا انتقلنا إلى لوحة التحكم (النتيجة المتوقعة)
cy.url().should('include', '/dashboard');
cy.contains('مرحباً بك، المستخدم التجريبي').should('be.visible');
});
it('should show an error message for invalid credentials', () => {
cy.visit('/login');
cy.get('input[name="email"]').type('testuser@example.com');
cy.get('input[name="password"]').type('WrongPassword');
cy.get('button[type="submit"]').click();
// التحقق من ظهور رسالة الخطأ
cy.get('.error-message').should('contain', 'البريد الإلكتروني أو كلمة المرور غير صحيحة');
});
});
عندما كتبنا أول اختبار ناجح ورأيناه يعمل آلياً في ثوانٍ معدودة، كان شعوراً رائعاً. لقد كان أول انتصار حقيقي لنا ضد فوضى “ماذا كسرنا هذه المرة؟”.
الخطوة الرابعة: الدمج مع مسار العمل (CI/CD Integration)
هذه هي الخطوة التي تحول الاختبارات من شيء “نشغّله يدوياً عند الحاجة” إلى “شبكة أمان تعمل تلقائياً”. قمنا بضبط نظام التكامل المستمر (Continuous Integration) لدينا (كنا نستخدم GitHub Actions) بحيث يتم تشغيل حزمة الاختبارات التراجعية بأكملها تلقائياً مع كل عملية `push` للكود إلى المستودع (Repository).
الآن، إذا قام أي مطور بإجراء تغيير يكسر عملية تسجيل الدخول عن غير قصد، سيفشل الاختبار الآلي في غضون دقائق، وسيتم إعلام الفريق فوراً، قبل أن يصل الكود المكسور إلى مرحلة النشر أو حتى إلى فرع الكود الرئيسي. لقد أصبح لدينا حارس آلي لا ينام ولا يمل.
نصائح من قلب الورشة (من خبرة أبو عمر)
- ابدأ صغيراً: لا تهدف للكمال. ابدأ باختبار واحد لمسار حرج واحد. الزخم أهم من الكمال في البداية.
- الاختبارات هي كود أيضاً: عامل ملفات الاختبارات بنفس الاهتمام الذي تعامل به كود التطبيق. اجعلها نظيفة، مقروءة، وسهلة الصيانة. استخدم متغيرات للبيانات المتكررة ولا تكتب نفس الكود مراراً وتكراراً.
- لا تختبر فقط “المسار السعيد” (Happy Path): تأكد من كتابة اختبارات للسيناريوهات الفاشلة أيضاً، مثل إدخال كلمة مرور خاطئة أو ترك حقل مطلوب فارغاً.
- اجعل الاختبارات مستقلة: يجب أن يكون كل اختبار قادراً على العمل بمفرده دون الاعتماد على اختبار آخر سبقه. هذا يمنع حدوث “فشل متسلسل” ويجعل تصحيح الأخطاء أسهل.
- اجعلها مسؤولية الفريق: لا تجعل الأتمتة وظيفة شخص واحد. شجع كل المطورين على تعلم كتابة الاختبارات والمساهمة فيها. جودة المنتج هي مسؤولية الجميع.
الخلاصة: من الروليت الروسية إلى الإبحار الهادئ ⛵️
العودة إلى قصتنا في البداية. هل اختفت الأخطاء تماماً؟ بالطبع لا. نحن بشر ونكتب برمجيات معقدة. لكن ما تغير بشكل جذري هو طريقة تعاملنا معها.
لم نعد نضغط على زر النشر وقلوبنا في أيدينا. أصبحنا ننشره بثقة، لأننا نعلم أن لدينا جيشاً من الاختبارات الآلية التي تعمل كشبكة أمان، تلتقط 90% من الأخطاء التراجعية الشائعة قبل أن ترى النور. الليالي الطوال التي كنا نقضيها في إصلاح ما كسرناه تحولت إلى وقت نقضيه في بناء ميزات جديدة ومبتكرة.
الاستثمار في الاختبار التراجعي الآلي ليس ترفاً، بل هو أحد أفضل الاستثمارات التي يمكن لأي فريق برمجيات القيام بها. إنه استثمار في الجودة، في سرعة التسليم، وفي أهم شيء: راحة بال الفريق. يلا، شدّوا حيلكم وابدأوا اليوم!