الوحدة 1 · لماذا نختبر، وماذا نختبر
ما الذي تمنحك إياه الاختبارات
10 دقراءة 3 دبيئة تجريب الكود
الدرس في لمحة
تشرح ما الذي تمنحه الاختبارات الآلية للفريق، وتسمّي الطبقات الأربع في كأس الاختبارات، وتختار الطبقة المناسبة لخطر معيّن في تطبيق صغير.
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
في الربيع الماضي غيّر زميل لي سطرًا واحدًا في دالة مساعدة لتقسيم الفواتير: صار Math.round هو Math.floor، كي “يصلح” حصّة بدت أعلى بسنت واحد. ظلّت كل الشاشات تعمل. وبعد ثلاثة أسابيع لاحظ مستخدم أن أرصدة مجموعته لم يعد مجموعها صفرًا، ولم يستطع أحد أن يحدّد أيّ واحد من 200 commit كان السبب. اختبار واحد من ثلاثة أسطر كان سيفشل خلال ثانية من ذلك التغيير.
هذا بالضبط ما تمنحك إياه الاختبارات: الثقة لتغيير الكود بسرعة. بدونها تصبح كل إعادة هيكلة (refactoring) مقامرة، ويحتاج كل إصدار إلى شخص يتنقّل في التطبيق نقرةً نقرة. ومع اختبارات جيدة، تغيّر الكود، وتشغّل مجموعة الاختبارات (suite)، فتعرف النتيجة.
ما هو الاختبار
انزع عنه كل أطر العمل، وسيبقى الاختبار كودًا يشغّل كودك ويرمي خطأً حين تكون النتيجة خاطئة:
function splitEvenly(totalCents, people) {
const base = Math.floor(totalCents / people);
const remainder = totalCents % people;
return Array.from({ length: people }, (_, i) => (i < remainder ? base + 1 : base));
}
const shares = splitEvenly(1000, 3);
const sum = shares.reduce((a, b) => a + b, 0);
if (sum !== 1000) throw new Error(`Expected shares to add up to 1000, got ${sum}`);
console.log('PASS shares add up to the total', shares);تضيف Vitest وJest وPlaywright الكثير فوق ذلك: رسائل فشل واضحة، ووضع المراقبة (watch mode)، والتشغيل المتوازي، والمتصفّحات. لكن في العمق، expect(sum).toBe(1000) ليست سوى if ترمي خطأً.
للاختبارات تكلفة أيضًا
كل اختبار كود عليك صيانته. يأخذ وقتًا لكتابته، ودقائق من CI لتشغيله، وانتباهًا حين يفشل. والاختبار الذي يفشل دون أن يكون شيء معطّلًا (الـ flaky test أو الاختبار المتقلّب، أو الاختبار الهشّ) أسوأ من غيابه: فالناس يتعلّمون تجاهل البناء الأحمر. لذا فالسؤال ليس أبدًا “كم اختبارًا؟” بل كم من الثقة يشتري كل اختبار مقابل كل دقيقة من CI.
كأس الاختبارات
كأس الاختبارات (testing trophy) طريقة شائعة لتصوّر الطبقات ومقدار ما تكتبه من كل منها:
- التحليل الساكن (TypeScript وESLint) يلتقط الأخطاء الإملائية والأنواع الخاطئة قبل أن يعمل أي شيء.
- الـ unit tests (اختبارات الوحدة) تفحص دالة واحدة أو وحدة برمجية واحدة بمعزل عن غيرها. سريعة ودقيقة؛ ومثالية للمنطق الصافي مثل تقريب المبالغ المالية.
- الـ integration tests (اختبارات التكامل) تفحص عدة وحدات تعمل معًا، مثل مكوّن نموذج يستدعي كود التحليل الحقيقي. تلتقط معظم الأخطاء الحقيقية بتكلفة معتدلة، ولهذا كان الكأس في أعرض حالاته عندها.
- الـ end-to-end tests (اختبارات من الطرف إلى الطرف، أو e2e) تقود التطبيق الحقيقي في متصفّح حقيقي. هي الأقرب إلى الواقع والأغلى ثمنًا؛ فاحتفظ بها للمسارات التي لو انكسرت لتصدّرت الأخبار.
القاعدة التي أستعملها كل يوم: اختر أرخص اختبار يمكن أن يفشل لو انكسر الشيء الذي يقلقك.
ما الذي ستبنيه في هذه الدورة
المشروع الذي يرافقك طوال الدورة هو Splitwise-lite، وحدة برمجية لتقسيم الفواتير في رحلة جماعية: تحليل المبالغ، وتقسيم الفواتير، وإضافة البقشيش، وحساب من يدين لمن، وتسوية الحسابات، إضافة إلى واجهة صغيرة وطبقة شبكة. في ساحة التجربة تحصل على كود يعمل وتكتب اختبارات له. ثم يشغّل المدقّق اختباراتك على نسخ معطوبة عمدًا (تقريب خاطئ، أو خطأ بفارق واحد، أو معاملات متبادلة الموقع)، وعلى اختباراتك أن تلتقط كل واحدة منها. النجاح على الكود السليم نصف المهمة؛ والفشل على الكود المعطوب هو النصف الآخر.
قبل أن تثبّت أي شيء، ابنِ الفكرة الأساسية بنفسك في التمرين التالي: assertion (تأكيد) يرمي خطأً، ومشغّل اختبارات (test runner) يلتقطه.
أهم النقاط
- الاختبار كود يشغّل كودك ويرمي خطأً حين تكون النتيجة خاطئة؛ وكل إطار عمل للاختبار مجرّد غلاف أجمل حول هذه الفكرة.
- تمنحك الاختبارات الثقة لتغيير الكود بسرعة؛ أما الاختبار الذي لا يلتقط خطأً أبدًا ولا يوثّق شيئًا فتكلفته أكبر من فائدته.
- لكأس الاختبارات (testing trophy) أربع طبقات (التحليل الساكن، والـ unit، والـ integration، والـ end-to-end)، وعادةً ما تمنحك الـ integration tests أكبر قدر من الثقة مقابل كل دقيقة من وقت CI.
- اختر أرخص اختبار يمكن أن يفشل لو انكسر الشيء الذي يقلقك.