EduFlow
اختبار تطبيقات JavaScript
⁦0%⁩

الوحدة 1 · لماذا نختبر، وماذا نختبر

ما الذي تمنحك إياه الاختبارات

10 دقراءة 3 دبيئة تجريب الكود

الدرس في لمحة

تشرح ما الذي تمنحه الاختبارات الآلية للفريق، وتسمّي الطبقات الأربع في كأس الاختبارات، وتختار الطبقة المناسبة لخطر معيّن في تطبيق صغير.

  1. 1ما هو الاختبار
  2. 2للاختبارات تكلفة أيضًا
  3. 3كأس الاختبارات
  4. 4ما الذي ستبنيه في هذه الدورة

لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.

في الربيع الماضي غيّر زميل لي سطرًا واحدًا في دالة مساعدة لتقسيم الفواتير: صار Math.round هو Math.floor، كي “يصلح” حصّة بدت أعلى بسنت واحد. ظلّت كل الشاشات تعمل. وبعد ثلاثة أسابيع لاحظ مستخدم أن أرصدة مجموعته لم يعد مجموعها صفرًا، ولم يستطع أحد أن يحدّد أيّ واحد من 200 commit كان السبب. اختبار واحد من ثلاثة أسطر كان سيفشل خلال ثانية من ذلك التغيير.

هذا بالضبط ما تمنحك إياه الاختبارات: الثقة لتغيير الكود بسرعة. بدونها تصبح كل إعادة هيكلة (refactoring) مقامرة، ويحتاج كل إصدار إلى شخص يتنقّل في التطبيق نقرةً نقرة. ومع اختبارات جيدة، تغيّر الكود، وتشغّل مجموعة الاختبارات (suite)، فتعرف النتيجة.

ما هو الاختبار

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

JavaScript
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) طريقة شائعة لتصوّر الطبقات ومقدار ما تكتبه من كل منها:

شكل كأس من أربع طبقات: التحليل الساكن في القاعدة، وطبقة unit رفيعة، وطبقة integration عريضة، وطبقة end-to-end صغيرة في القمة. End-to-end Integration مكوّنات ووحدات معًا Unit ساكن: أنواع + lint أبطأ، أغلى، أقرب للواقع
كأس الاختبارات: الـ integration tests تمنح أكبر ثقة مقابل تكلفتها
  • التحليل الساكن (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.
  • اختر أرخص اختبار يمكن أن يفشل لو انكسر الشيء الذي يقلقك.

قراءات إضافية

تحقّق من فهمك

3 أسئلة

السؤال 1 من 3

يقرّب Splitwise-lite حصّة كل شخص إلى أقرب سنت كامل. أيّ اختبار يعطيك أسرع إشارة وأدقّها على أن التقريب صحيح؟

السؤال 2 من 3

ماذا يفعل إطار الاختبار في جوهره حين يفشل assertion؟

السؤال 3 من 3

تستغرق مجموعة اختبارات الـ end-to-end في فريقك 40 دقيقة، وتفشل عشوائيًا مرة في اليوم تقريبًا. أيّ تغيير يرفع الثقة مقابل كل دقيقة من CI أكثر من غيره؟

تدريب

بيئة تجريب الكود

ابنِ مشغّل اختبارات من عشرة أسطر

اكتب الدالتين اللتين يقوم عليهما كل إطار اختبار.

  • expectEqual(actual, expected) لا تفعل شيئًا حين تكون القيمتان متساويتين تمامًا (===)، وترمي Error في غير ذلك. يجب أن تتضمّن الرسالة القيمتين معًا، مثل Expected 334 but received 333.
  • test(name, fn) تستدعي fn. إن انتهت بشكل طبيعي، اطبع PASS <name>. وإن رمت خطأً، اطبع FAIL <name>: <error message> ولا تُعِد رمي الخطأ، كي يعمل الاختبار التالي.

شغّل الكود: الاختبار الثاني يفشل عمدًا، لأن 0.1 + 0.2 لا يساوي 0.3 تمامًا في JavaScript.

// An assertion: throw an Error when actual !== expected.
function expectEqual(actual, expected) {
  // your code
}

// A runner: run fn, log PASS or FAIL, never let one failure stop the others.
function test(name, fn) {
  // your code
}

test("two plus two", () => expectEqual(2 + 2, 4));
test("deliberately wrong", () => expectEqual(0.1 + 0.2, 0.3));
جارٍ تحميل المحرّر…

Ctrl/⌘ + Enter للتشغيل · يعمل في بيئة معزولة داخل متصفّحك. لا شيء يغادر جهازك.

الـ console

(لا مخرجات)

الفحوص

  • لا ترمي expectEqual خطأً حين تتساوى القيمتان
  • ترمي expectEqual خطأً من نوع Error يذكر القيمتين
  • تطبع test السطر PASS للاختبار الناجح
  • تطبع test السطر FAIL : للاختبار الفاشل
  • تلتقط test الخطأ بدل إعادة رميه