تغطية الشروط والمسارات
تنظر تغطية الشروط (Condition Coverage) إلى داخل القرار. فالقرار account.active && (amount <= account.dailyLimit || otpOk) يضم ثلاثة شروط ذرّية (Atomic conditions)، أي تعابير منطقية لا تحتوي على && أو ||. تسأل تغطية الفروع هل كان القرار ككل صحيحاً وخاطئاً، وتسأل تغطية الشروط هل كان كل شرط كذلك. أما تغطية المسارات (Path Coverage) فتنظر إلى الدالة كلها: هل نُفّذ كل طريق من نقطة الدخول إلى نقطة الخروج؟
يتجاوز المعياران تغطية التعليمات وتغطية الفروع من الدرسين السابقين، ولا يصلح أيٌّ منهما هدفاً بنسبة 100% في كل مكان. فالمهارة هي اختيار المجموعة الجزئية المناسبة للمنطق عالي المخاطر والتصريح بما اخترته.
لماذا يهم
يكفي اختباران لتغطية فروع القرار كاملةً مهما كان عدد شروطه. لنفترض أن المطور نسي الأقواس: account.active && amount <= account.dailyLimit || otpOk. ولأن أسبقية && أعلى من أسبقية ||، صار الحساب الموقوف الذي يؤكد رمز التحقق لمرة واحدة (OTP) يحصل على الموافقة على تحويله. وإذا كان اختبارا تغطية الفروع هما «كل شيء صحيح» و«كل شيء خاطئ»، أعطت النسختان النتائج نفسها ومرّ العيب.
العوامل المنطقية الخاطئة والأقواس الناقصة والشروط التي لا أثر لها عيوب نموذجية في القرارات المركّبة، وكثيراً ما تقع في القواعد التي تقرر المال والصلاحيات. والمعايير على مستوى الشروط تختار تركيبات قليلة تفحص أثر كل شرط في القرار، وهناك تظهر هذه العيوب، دون اختبار كل التركيبات.
الشروط والقرارات وتغطية الشروط
كما في الدرس السابق، القرار نقطة تختار فيها الشيفرة بين نتائج، مثل جملة if أو شرط حلقة. والشروط هي التعابير المنطقية داخله؛ والشرط الذرّي في مسرد ISTQB هو الشرط الذي لا يحتوي على أي عامل منطقي. والدالة أدناه فيها قرار واحد بثلاثة شروط ذرّية: A هو account.active، وB هو amount <= account.dailyLimit، وC هو otpOk.
تقيس تغطية الشروط نتائج الشروط التي نفّذتها الاختبارات: الصحيح والخاطئ لكل شرط ذرّي. وهي لا تتضمن تغطية الفروع. فاختباران، حساب نشط تجاوز حدّه بلا رمز تحقق (A صحيح، وB خاطئ، وC خاطئ) وحساب موقوف ضمن حدّه مع رمز تحقق (A خاطئ، وB صحيح، وC صحيح)، يعطيان تغطية شروط بنسبة 100%، ومع ذلك يُرفض التحويلان ولا يُنفَّذ APPROVED أبداً.
- تغطية الشروط والقرارات (Condition/Decision Coverage، وتُسمّى أيضاً Decision Condition Coverage) تسدّ الثغرة: فهي تشترط كل نتيجة لكل شرط وكل نتيجة لكل قرار.
- التقييم المختصر (Short-circuit evaluation، ويسمّيه مسرد ISTQB باسم Short-circuiting): في JavaScript وJava وC# يتوقف
&&و||بمجرد معرفة النتيجة (وكذلكandوorفي Python)، فإذا كان A خاطئاً لا يُقيَّم B ولا C أصلاً. ويصمّم هذا الدرس الاختبارات على جدول الحقيقة، لكن اختبار الحساب الموقوف أعلاه لا يُقيّم B ولا C عند التشغيل، فالأداة التي تقيس الشروط لن تحتسب لأيٍّ منهما النتيجة «صحيح». والأدوات تختلف أيضاً: بعضها يعدّ كل طرف فرعاً، وبعضها لا يقيس إلا القرارات الكاملة أو الأسطر؛ فافحص أداتك قبل الإبلاغ عن تغطية الشروط.
1 function reviewTransfer(account: Account, amount: number, otpOk: boolean) {
2 if (account.active && (amount <= account.dailyLimit || otpOk)) {
3 return 'APPROVED';
4 }
5 return 'REJECTED';
6 }مثال تطبيقي: من تغطية الشروط إلى MC/DC
المتطلب: «يُوافَق على التحويل ضمن الحد اليومي إذا كان الحساب نشطاً. وفوق الحد يحتاج إلى تأكيد رمز التحقق OTP. وتُرفض دائماً التحويلات من الحسابات الموقوفة». ثلاثة شروط تعطي 2 × 2 × 2 = 8 تركيبات: اكتبها في جدول حقيقة (Truth table) كما في درس قواعد العمل، مع القرار المتوقع من المتطلب.
تشترط تغطية الشروط والقرارات المعدّلة (Modified Condition/Decision Coverage, MC/DC) إثبات أن كل شرط يغيّر القرار وحده. والطريقة المعتادة زوج استقلال (Independence pair): اختباران لا يختلفان إلا في ذلك الشرط ويعطيان قرارين مختلفين. ولإيجاد زوج B ثبّت A وC، واقلب قيمة B، وابحث عن تغيّر في القرار: حين يكون A صحيحاً وC خاطئاً يعطي الصفان 2 و4 صحيحاً ثم خاطئاً. ويشترط معيار الطيران DO-178C تغطية MC/DC لأكثر مستويات البرمجيات حرجاً فيه، وهو المستوى A.
A && (B || C): الصفان 4 و5 يعطيان تغطية شروط كاملة والقرار خاطئ فيهما، والصفان 1 و8 يعطيان تغطية الشروط والقرارات، ومجموعة MC/DC المختارة هي 2 و3 و4 و7، وتغطية الشروط المتعددة تحتاج إلى الثمانية.| الصف · A B C | القرار | تغطية الشروطاختباران | تغطية الشروط والقراراتاختباران | MC/DC4 اختبارات | تغطية الشروط المتعددة8 اختبارات |
|---|---|---|---|---|---|
1T T T | T | ليس ضمن هذه المجموعة | اختبار | ليس ضمن هذه المجموعة | اختبار |
2T T F | T | ليس ضمن هذه المجموعة | ليس ضمن هذه المجموعة | يُثبت B (مع 4) | اختبار |
3T F T | T | ليس ضمن هذه المجموعة | ليس ضمن هذه المجموعة | يُثبت A (مع 7) وC (مع 4) | اختبار |
4T F F | F | اختبار | ليس ضمن هذه المجموعة | يُثبت B (مع 2) وC (مع 3) | اختبار |
5F T T | F | اختبار | ليس ضمن هذه المجموعة | ليس ضمن هذه المجموعة | اختبار |
6F T F | F | ليس ضمن هذه المجموعة | ليس ضمن هذه المجموعة | ليس ضمن هذه المجموعة | اختبار |
7F F T | F | ليس ضمن هذه المجموعة | ليس ضمن هذه المجموعة | يُثبت A (مع 3) | اختبار |
8F F F | F | ليس ضمن هذه المجموعة | اختبار | ليس ضمن هذه المجموعة | اختبار |
- تركيبة يختبرها هذا المعيار
- الصف 7، حساب موقوف تجاوز حدّه مع رمز التحقق: الاختبار الذي يكشف الأقواس الناقصة
الشرط A هو account.active، وB هو amount <= account.dailyLimit، وC هو otpOk. والرمز T يعني صحيحاً وF خاطئاً، والقرار T يعني الموافقة على التحويل.
- تغطية الشروط: الصفان 4 و5، والقرار خاطئ فيهما. تغطية الشروط والقرارات: الصفان 1 و8.
- MC/DC: الصفوف 2 و3 و4 و7. يُثبَت أثر B بالصفين 2 و4، وC بالصفين 3 و4، وA بالصفين 3 و7. أي أربعة اختبارات لثلاثة شروط: فتغطية MC/DC تحتاج دائماً إلى اختبار واحد على الأقل زيادةً على عدد الشروط.
- يمكن أن يستخدم A الصفين 2 و6 أيضاً. اختر 3 و7: فالصف 7، حساب موقوف تجاوز حدّه مع رمز التحقق، يكشف عيب الأقواس الناقصة. وحين يترك لك المعيار خياراً، اختر بحسب المخاطر.
- تغطية الشروط المتعددة (Multiple Condition Coverage): الصفوف الثمانية كلها. وتتضاعف مع كل شرط؛ فعشرة شروط تعني 1,024 تركيبة.
النسخة النصية
Decision: A && (B || C)
A = account.active B = amount <= account.dailyLimit C = otpOk
Row A B C Decision MC/DC independence pairs
1 T T T T
2 T T F T B: rows 2 and 4
3 T F T T C: rows 3 and 4, A: rows 3 and 7
4 T F F F B: rows 2 and 4, C: rows 3 and 4
5 F T T F
6 F T F F
7 F F T F A: rows 3 and 7
8 F F F F
Condition coverage rows 4, 5 decision never T
Condition/decision coverage rows 1, 8
MC/DC rows 2, 3, 4, 7
Multiple condition coverage all 8 rowsتغطية المسارات، ولماذا ينفجر عددها مع الحلقات
المسار (Path) طريق واحد عبر الشيفرة من نقطة الدخول إلى نقطة الخروج، وتسأل تغطية المسارات هل نفّذت الاختبارات كل طريق. والمسارات تتضاعف: ثلاث جمل if متتالية لها 8 مسارات، في حين يكفي اختباران لتغطية كل فروعها.
والحلقات أسوأ بكثير، لأن كل عدد من التكرارات مسار مختلف. ففي cartTotal يسلك كل عنصر أحد طريقين، فللسلة التي تضم n من العناصر 2^n من المسارات: 1,048,576 مساراً لسلة ممتلئة بعشرين عنصراً، و2,097,151 مساراً إجمالاً. أما الحلقة التي لا حدّ أعلى لتكراراتها فعدد مساراتها لا نهائي. وبعض المسارات غير قابل للتنفيذ (Infeasible) أيضاً: شروطها متناقضة فلا يستطيع أي مُدخل تنفيذها.
- في الحلقات طبّق القيم الحدّية على عدد التكرارات: صفر، وواحد، وعدد معتاد، والحد الأقصى (هنا 0 و1 و5 و20 عنصراً)، وأضف 21 عند إضافة العناصر، وهو ما يجب أن ترفضه السلة.
- يكشف التفكير في المسارات عيوباً تحتاج إلى تسلسل من الطرق، مثل حالة متبقية من تكرار تُفسد التكرار التالي.
1 function cartTotal(items: Item[]): number { // a cart holds 0 to 20 items
2 let total = 0;
3 for (const item of items) {
4 if (item.onSale) {
5 total = total + item.salePrice;
6 } else {
7 total = total + item.price;
8 }
9 }
10 return total;
11 }
Items in cart Paths
0 1
1 2
2 4
3 8
20 1,048,576
0 to 20 2,097,151 in totalالمسارات الأساسية والتعقيد الدوري
اختبار المسارات الأساسية (Basis Path Testing)، وهو جوهر منهج الاختبار المهيكل (Structured testing) في NIST SP 500-235، يُبقي المسارات قابلة للإدارة. والتعقيد الدوري (Cyclomatic Complexity)، ويُكتب v(G)، هو أكبر عدد من المسارات المستقلة خطياً عبر مخطط تدفق التحكم (Control flow graph) للدالة: v(G) = edges − nodes + 2، أي عدد الحواف (Edges)، وهي فروع المخطط، ناقص عدد العُقد (Nodes) زائد 2. وحين تكون كل القرارات ثنائية الاتجاه يساوي ذلك عدد القرارات زائد واحد. والمجموعة الأساسية (Basis set) هي v(G) من المسارات يمكن تركيب أي مسار آخر منها.
في cartTotal قراران، الحلقة وجملة if، لذا v(G) = 3، وتكوّن ثلاثة اختبارات مجموعة أساسية: سلة فارغة، وعنصر عادي واحد، وعنصر مخفّض واحد. والمجموعة الأساسية تضمن تغطية الفروع كاملةً لا كل تركيبة: إنها حدّ أدنى منهجي، لا تغطية كاملة للمسارات.
- بعدّ جملة
ifوحدها تكونv(G) = 2فيreviewTransfer. أما NIST SP 500-235 فيعدّ أيضاً كل&&و||ذي تقييم مختصر قراراً، لأن كلاً منهما قد يتخطى بقية التعبير، فتصبح 4؛ وكذلك تعدّهما القاعدةcomplexityفي ESLint. وقد تختلف الأدوات، فلا تقارن إلا أرقام الأداة نفسها. - اعتبر ارتفاع
v(G)إشارة: فالدالة تحتاج إلى اختبارات كثيرة ويصعب فهمها، فهي تستحق مراجعة أدق أو إعادة هيكلة.
أخطاء شائعة
- قراءة تغطية شروط بنسبة 100% على أنها «كل قرار سار في الاتجاهين».
- اختبار قرار مركّب مثل
a && (b || c)مرة صحيحاً ومرة خاطئاً فقط، فيمرّ عامل منطقي خاطئ أو أقواس ناقصة. - افتراض أن اختباراً نفّذ شرطاً تخطّاه التقييم المختصر.
- استهداف تغطية مسارات بنسبة 100% في شيفرة فيها حلقات بدلاً من المسارات الأساسية وأعداد تكرار مختارة.
- الإبلاغ عن التركيبات المستحيلة أو المسارات غير القابلة للتنفيذ على أنها ثغرات بدلاً من تسجيلها مع السبب.
- اختيار أزواج MC/DC آلياً بدلاً من الزوج الذي يختبر التركيبة الأعلى خطراً.
من منظور ضمان الجودة
استخدم المعايير على مستوى الشروط للمنطق عالي المخاطر مثل المدفوعات والصلاحيات والأهلية. وفي المراجعات اطلب من المطور أن يشرح لك القرارات المركّبة. ضع جدول الحقيقة في تصميم الاختبار، وعلّم الصفوف المستحيلة مع السبب، وحوّل الصفوف المختارة إلى حالات اختبار مبنية على البيانات (Data-driven) نتائجها المتوقعة من المتطلب.
في التقارير سمِّ المعيار والنطاق: «قاعدة اعتماد التحويل: MC/DC بأربعة اختبارات». ونبّه مبكراً إلى التعقيد الدوري المرتفع أو القرار كثير الشروط: فهو يبيّن عدد الاختبارات التي يحتاجها المنطق، وقد يكون سبباً لتبسيط الشيفرة أولاً.
تمرين عملي
صمّم اختبارات MC/DC لقاعدة العمر
المتطلب: «يمكن لمن بلغ 18 عاماً فأكثر فتح حساب. ومن عمره من 13 إلى 17 عاماً يحتاج إلى موافقة أحد الوالدين. ولا يمكن لمن هو دون 13 عاماً فتح حساب». وفيما يلي دالة المطور واختبارا الوحدة الخاصان بها، وهما يبلغان تغطية فروع بنسبة 100%.
صمّم اختبارات على مستوى الشروط بأعمار فعلية، واكتشف ما فات اختبارَي الوحدة.
1 function canOpenAccount(age: number, parentConsent: boolean): boolean {
2 if (age >= 18 || (parentConsent && age > 13)) {
3 return true;
4 }
5 return false;
6 }
Unit tests Expected Result
U1 canOpenAccount(25, true) true passed
U2 canOpenAccount(9, false) false passed- سمِّ الشروط الذرّية في السطر 2 واكتب جدول الحقيقة دون الصفوف المستحيلة.
- جِد زوج استقلال لكل شرط، واجمع الأزواج في أصغر مجموعة تحقق MC/DC.
- اختر أعماراً عند حدود المتطلب، مع النتائج المتوقعة منه.
- تتبّع الاختبارات: أيها يفشل، وما الذي لم يفحصه
U1وU2قط؟
إظهار الإجابة النموذجية
A هو age >= 18، وB هو parentConsent، وC هو age > 13؛ والقرار A || (B && C). ولا يمكن أن يكون A صحيحاً وC خاطئاً (فكل عمر من 18 فأكثر أكبر من 13)، فتبقى 6 تركيبات من 8.
بكتابة كل صف بترتيب A ثم B ثم C: يُثبَت A بالصفين TFT (صحيح) وFFT (خاطئ)، وB بالصفين FTT وFFT، وC بالصفين FTT وFTF. وأصغر مجموعة MC/DC هي TFT وFFT وFTT وFTF: أربعة اختبارات.
الأعمار: 18 دون موافقة ← مسموح؛ 17 دون موافقة ← مرفوض؛ 17 بموافقة ← مسموح؛ 12 بموافقة ← مرفوض. ويضيف تحليل القيم الحدّية 13 بموافقة ← مسموح.
هذا الاختبار يفشل: 13 > 13 خاطئ، فتضع الشيفرة العمر 13 في صف العمر 12 نفسه وترفض حساباً يسمح به المتطلب. أبلغ عن عيب الفارق بواحد في السطر 2؛ والإصلاح هو age >= 13.
بلغ U1 وU2 تغطية فروع بنسبة 100%، لكن العمر وحده حسم النتيجة في U1 ولم تكن في U2 موافقة، فلم تُختبر قاعدة الموافقة قط. كشفت MC/DC التركيبات الناقصة، واختار تحليل القيم الحدّية القيمة التي كشفت العيب.
أسئلة شائعة
هل تتضمن MC/DC تغطية القرارات؟
نعم، في القرارات التي تُطبَّق عليها: فكل زوج استقلال يُظهر القرار مرة صحيحاً ومرة خاطئاً. ولا تقول شيئاً عن الشيفرة خارج هذه القرارات، ولا عن سلوك لم يُنفَّذ أصلاً.
أي معيار أستخدم؟
اجعل تغطية الفروع خط الأساس. وللقرارات المركّبة في القواعد عالية المخاطر أضف تغطية الشروط والقرارات أو MC/DC. وتغطية الشروط المتعددة معقولة التكلفة حين يضم القرار شرطين أو ثلاثة. أما التغطية الكاملة للمسارات فتناسب الدوال الصغيرة الخالية من الحلقات فقط؛ وفيما عداها اختبر مجموعة أساسية وأعداد التكرار المهمة.
هل عليّ حساب التعقيد الدوري يدوياً؟
نادراً. أدوات التحليل الساكن وأدوات الفحص (Linters) تحسبه لكل دالة؛ فالقاعدة complexity في ESLint مثلاً تنبّه إلى كل دالة تتجاوز حداً تضبطه أنت. لكن حسابه يدوياً مرة واحدة لدالة صغيرة يساعدك على فهم ما يعدّه الرقم ولماذا قد تختلف الأدوات.
إذا غطّيت كل مسار، فهل اختُبرت الدالة بالكامل؟
لا. قد ينجح المسار مع بيانات ويفشل مع غيرها، مثل قسمة لا تفشل إلا عندما تكون قيمة ما صفراً، والمتطلب الذي لم يُنفَّذ قط ليس له مسار أصلاً. تقيس تغطية المسارات الطرق عبر الشيفرة، لا صحة النتائج.
المراجع
- ISTQB Glossaryالتعريفات القياسية لمصطلحات الاختبار — حالة الاختبار واختبار الانحدار والخطورة والأولوية وتحليل القيم الحدّية وغيرها.
- NIST SP 500-235: Structured Testing: A Testing Methodology Using the Cyclomatic Complexity Metricالاختبار المهيكل (اختبار المسارات الأساسية): استخدام مقياس التعقيد الدوري لـ McCabe لاشتقاق مجموعة أساسية من المسارات المستقلة لاختبارها — وهو أشمل من تغطية التعليمات والفروع.
- ISTQB Certified Tester Foundation Level (CTFL) v4.0المنهج التأسيسي لاختبار البرمجيات: مستويات الاختبار وأنواعه وتقنيات تصميم الاختبارات وإدارة الاختبار والأدوات.
تدرّب بأدوات متقن
أنشئ جداول القرار من الشروط والإجراءات، وولّد جميع تركيبات القواعد، واختزل المستحيل منها.
ولّد القيم الحدّية (min-1 وmin وmin+1 والقيمة الاسمية وmax-1 وmax وmax+1) للقيود الرقمية وقيود الطول.
نفّذ تحليلاً كاملاً للقيم الحدّية (ثنائي القيم وثلاثي القيم) مع فئات التكافؤ والنتائج المتوقّعة.
للتعمّق أكثر
تقدّمك في هذا الموضوع
سجّل الدخول لتتابع تقدّمك
علّم الموضوعات مكتملة، وتابع من حيث توقفت، وافتح امتحانات التخصص.