الوحدة 5 · استراتيجية الاختبار
الـ Coverage وحدودها
18 دقراءة 3 دبيئة تجريب الكود
الدرس في لمحة
تقيس الـ coverage (نسبة التغطية) في Vitest، وتقرأ التقرير لتجد الكود غير المختبَر، وتشرح لماذا قد يُخفي رقم تغطية مرتفع مجموعة اختبارات ضعيفة.
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
كل فريق يضيف اختبارات يسأل في النهاية “كم يكفي؟”، والـ coverage هي الرقم الذي يبدو أنه يجيب. لكنها تجيب عن سؤال أضيق مما يظنّه الناس. إن استُخدمت جيدًا، فهي طريقة سريعة لإيجاد كود لم يختبره أحد. وإن استُخدمت كدرجة تقييم، فهي تكافئ الاختبارات الخاطئة.
تشغيلها
تدعم Vitest مزوّدَين للـ coverage. الافتراضي، v8، يستخدم التغطية المدمجة في محرّك JavaScript ولا يحتاج إلى خطوة تجهيز للكود (instrumentation)؛ أما istanbul فيجهّز الكود ويعمل في بيئات تشغيل أكثر. ثبّت المزوّد وشغّل مع --coverage:
npm install --save-dev @vitest/coverage-v8
npx vitest run --coverageاضبط ما تقيسه وطريقة الإبلاغ عنه:
/// <reference types="vitest/config" />
import { defineConfig } from 'vite';
export default defineConfig({
test: {
coverage: {
provider: 'v8',
include: ['src/**/*.{js,jsx}'],
exclude: ['src/**/*.test.{js,jsx}', 'src/main.jsx'],
reporter: ['text', 'html'],
thresholds: { lines: 80, branches: 75 },
},
},
});include مهم: في Vitest 4، بدونه لا يسرد التقرير إلا الملفات التي صادف أن حمّلتها اختباراتك، فلا تظهر الوحدة غير المختبَرة كليًا أصلًا، وتبدو النسبة أفضل مما هي. ومع include، تظهر الملفات غير المختبَرة بنسبة 0%. ويكتب مُبلّغ html الملف coverage/index.html، الذي تفتحه في المتصفّح.
الأرقام الأربعة
- Statements (العبارات): كم عبارة عملت.
- Branches (الفروع): كم جهة من كل قرار عملت (
if/else، و? :، و&&، و||، و??، والمعاملات الافتراضية). - Functions (الدوال): كم دالة استُدعيت مرة واحدة على الأقل.
- Lines (الأسطر): كم سطرًا عمل فيه شيء ما.
تغطية الفروع هي ما أنظر إليه. const share = people > 0 ? total / people : 0; سطر واحد وفرعان. اختبار بثلاثة أشخاص يجعل السطر مغطّى بنسبة 100% والفروع بنسبة 50%، والفرع غير المختبَر هو بالضبط الحالة الحدّية من القسم 2.
قراءة التقرير
يعلّم تقرير HTML الأسطر التي لم تعمل قط بالأحمر، والفروع التي سُلكت في اتجاه واحد فقط بالأصفر. وهذا هو الجزء الثمين: الكود الأحمر غير مختبَر، بكل تأكيد. لا يمكن لأي assertion في أي مكان أن يكون قد فحصه، لأنه لم يعمل قط. حين أنضمّ إلى مشروع، يكون تقرير الـ coverage أسرع خريطة لأماكن الخطر: معالجة الأخطاء غير المغطّاة في وحدة الدفع، وفرع else الذي لم يصل إليه أحد.
ما لا تستطيع الـ coverage رؤيته
تسجّل الـ coverage التنفيذ. ولا تعرف شيئًا عمّا إذا كان أي شيء قد فحص النتيجة. هذا الاختبار يجعل computeBalances مغطّاة بنسبة 100%:
test('computes balances', () => {
const balances = computeBalances([
{ paidBy: 'Ana', amountCents: 3000, splitAmong: ['Ana', 'Ben', 'Cai'] },
]);
expect(Object.keys(balances)).toHaveLength(3);
});عمل كل سطر. وسينجح الـ assertion حتى لو خُصم المبلغ من الدافع بدل أن يُضاف إليه، أو أُخذ الجزء الصحيح من الحصص فاختفى سنت، أو استبدل مصروف ثانٍ الأول. الـ coverage: 100%. الثقة: قريبة من الصفر. ويبدأ التمرين من هذا الاختبار بالضبط.
السطر غير المغطّى سؤال، لا أمر. أحيانًا تكون الإجابة الصحيحة اختبارًا. وأحيانًا تكون حذف الكود: فرع لا يستطيع أحد الوصول إليه، أو بديل احتياطي لمتصفّح توقّفت عن دعمه، أو دالة مساعدة لم يعد شيء يستدعيها. والكود الميت “المغطّى” باختبار كُتب فقط لرفع الرقم هو أسوأ النتائج، لأنه يبدو الآن مهمًّا.
كم يكفي؟
لا يوجد رقم عالمي. لكن هذه قواعد تقريبية مفيدة:
- استهدف تغطية فروع عالية للمنطق الأساسي (المال، والصلاحيات، وتحويلات البيانات)، واقبل بأقل لكود الربط وتوصيلات الواجهة التي تشغّلها اختبارات الـ end-to-end على أي حال.
- استثنِ الكود الذي لا تكتب له unit tests عن قصد (نقاط الدخول، والملفات المولّدة، والوحدات التي تحتوي أنواعًا فقط) كي يكون للرقم معنى.
- انظر إلى الـ coverage على الـ diff في مراجعة الكود: هل أضاف هذا التغيير فروعًا غير مختبَرة؟ وهذا أنفع عمليًا من نسبة على مستوى المشروع كله.
تخبرك الـ coverage أين تنقص الاختبارات. ولتعرف إن كانت الاختبارات الموجودة جيدة أصلًا، تحتاج إلى أداة مختلفة، وهي موضوع الدرس القادم. لكن أولًا، في التمرين، حوّل مجموعة تغطيتها 100% ولا تلتقط شيئًا إلى مجموعة تلتقط أخطاءً حقيقية.
أهم النقاط
- تقيس الـ coverage أيّ العبارات والفروع والدوال والأسطر عملت أثناء الاختبارات، لا إن كان شيء قد فحص النتائج.
- أنفع ما في تقرير الـ coverage هو الأحمر والأصفر اللذان يُظهرهما لك؛ فالكود غير المغطّى كود غير مختبَر بكل تأكيد.
- تغطية الفروع (branch coverage) أكثر صرامة وأنفع من تغطية الأسطر، لأن السطر الواحد قد يحمل عدة قرارات.
- استخدم الحدود الدنيا (thresholds) كأرضية تمنع التغطية من الانزلاق، لا كهدف تسعى إليه؛ فالرقم الذي يصبح هدفًا يُتلاعب به.