اختبار الصندوق الأبيض وتغطية الشيفرة
اختبار الصندوق الأبيض (White-box Testing) يشتق الاختبارات ويقيسها انطلاقاً من البنية الداخلية لموضوع الاختبار، وهي الشيفرة في الغالب. فبدلاً من الاكتفاء بسؤال «ماذا يجب أن يفعل البرنامج؟» تسأل: «أيّ أجزاء الشيفرة شغّلتها اختباراتي، وأيّ مُدخل سيشغّل الباقي؟». مثال: لم يمرّ أي اختبار قط بالجانب true من جملة if؛ فتقرأ الشرط، وتختار مُدخلاً يجعله صحيحاً، وتأخذ النتيجة المتوقعة من المتطلب.
وتغطية الشيفرة (Code Coverage) هي القياس الذي يقوم عليه ذلك: أداة تسجّل أيّ التعليمات والفروع والدوال جرى تشغيلها أثناء الاختبارات، وتُعلّم الباقي سطراً سطراً.
لماذا يهم
تُشتق اختبارات الصندوق الأسود من المتطلبات، فلا تستطيع أن تُظهر مقدار ما وصلت إليه من التنفيذ الفعلي. والشيفرة كثيراً ما تحوي أكثر مما يقوله المتطلب: معالجة الأخطاء، وحالات خاصة، وقواعد قديمة، واختصاراً أضافه مطوّر. وإذا لم يشغّل أي اختبار تلك الشيفرة، وصلت عيوبها إلى بيئة الإنتاج دون أن يراها أحد.
وبالنسبة إلى المختبِر، تحوّل التغطية سؤال «هل اختبرنا بما يكفي؟» إلى أسئلة محددة: أيّ الفروع لم يصل إليها أي اختبار، وهل يذكر المتطلب ما يجب أن يحدث هناك؟ وحتى المختبِر الذي لا يكتب اختبارات الوحدة يستخدم الشيفرة غير المغطاة ليجد ثغرات في أساس الاختبار نفسه.
الصندوق الأبيض والصندوق الأسود: مصدران للاختبارات
يعرّف مسرد ISTQB اختبار الصندوق الأبيض بأنه اختبار مبني على تحليل البنية الداخلية للمكوّن أو النظام. وأساس الاختبار (Test basis) هنا هو التنفيذ نفسه: الشيفرة، وكذلك المعمارية ومسارات العمل وتدفقات البيانات. ومن هذه البنية تأتي بنود التغطية (Coverage items)، مثل تعليمات دالة ما أو فروعها، ولذلك لا يمكن تصميم هذه الاختبارات إلا بعد وجود التصميم أو الشيفرة.
أما تقنيات الصندوق الأسود، مثل تقسيم التكافؤ وتحليل القيم الحدّية، فتنطلق من السلوك المحدد في المواصفات، فيمكن تصميمها قبل كتابة أي شيفرة، وتبقى صالحة أياً كانت طريقة كتابتها. ويرى المنظوران أشياء مختلفة:
- نقطة القوة: يأخذ اختبار الصندوق الأبيض التنفيذ كاملاً في الحسبان، فيكشف العيوب حتى حين تكون المواصفات غامضة أو قديمة أو ناقصة.
- نقطة الضعف: المتطلب الذي لم يُنفَّذ قط لا يترك شيفرة تُغطّى، فيبقى هذا الإغفال غير مرئي. واختبارات الصندوق الأسود المشتقة من المتطلب هي التي تكشفه.
- يمكن تطبيقه دون تشغيل الشيفرة أيضاً: ففي المراجعة أو التشغيل الجاف (Dry run) تتتبّع المدخلات عبر المنطق.
- ينطبق على كل مستويات الاختبار: الفروع في اختبار المكوّنات، والاستدعاءات بين المكوّنات في اختبار التكامل، وخطوات مسار العمل في اختبار النظام.
اختبار الصندوق الأسودمبني على المواصفات
اختبارات مما يجب أن يفعله البرنامج
- أساس الاختبار:
- المتطلبات وقصص المستخدم والمواصفات
- بنود التغطية:
- الأقسام والقيم الحدّية
- متى يمكن تصميم الاختبارات:
- قبل وجود أي شيفرة
- ما يكشفه:
- سلوكاً يخالف المتطلب، ومتطلبات لم تُنفَّذ قط
- ما لا يراه:
- مقدار ما وصلت إليه الاختبارات من التنفيذ
اختبار الصندوق الأبيضمبني على البنية
اختبارات من طريقة بناء البرنامج
- أساس الاختبار:
- الشيفرة والمعمارية ومسارات العمل وتدفقات البيانات
- بنود التغطية:
- التعليمات والفروع والشروط والمسارات
- متى يمكن تصميم الاختبارات:
- بعد وجود التصميم أو الشيفرة فقط
- ما يكشفه:
- عيوباً في شيفرة لا تصفها المتطلبات، حتى حين تكون المواصفات غامضة أو ناقصة
- ما لا يراه:
- المتطلبات التي لم تُنفَّذ قط: لا شيفرة، فلا شيء يُغطّى
تستخدم الفرق الاثنين معاً: الاختبارات المبنية على المتطلبات أولاً، ثم اختبارات الصندوق الأبيض للشيفرة التي لم تصل إليها.
كيف تُقاس تغطية الشيفرة
تُجهّز أداة التغطية الشيفرة بعدّادات (Instrumentation)، أو تطلب من بيئة التشغيل تسجيل ما يُنفَّذ، ثم تشغّل الاختبارات وتكتب تقريراً: لكل ملف نسبة التعليمات أو الأسطر والفروع والدوال التي جرى تشغيلها، ونسخة من الشيفرة تبيّن حال كل سطر: نُفّذ، أو لم يُنفَّذ، أو نُفّذ جزئياً. وتغطية الأسطر قريبة من تغطية التعليمات لكنها ليست مطابقة لها، فالسطر الواحد قد يحوي عدة تعليمات.
وتتفاوت المعايير في قوتها: تغطية التعليمات (Statement Coverage) تسأل هل نُفّذت كل تعليمة قابلة للتنفيذ، وتغطية الفروع (Branch Coverage) تسأل هل نُفّذ كل فرع، كالنتيجتين الصحيحة والخاطئة لكل جملة if. وتتناول الدروس التالية تغطية التعليمات والفروع والشروط والمسارات واحدة تلو الأخرى. ومن الأدوات الشائعة:
- JavaScript وTypeScript: أداة Istanbul (الأمر
nyc، وهي أيضاً ما يستخدمه الخيار--coverageفي Jest افتراضياً)، أو التغطية المدمجة في محرك V8، التي يستطيع Jest وVitest استخدامها أيضاً. - Python: أداة coverage.py، وتقيس التعليمات افتراضياً والفروع مع الخيار
--branch. - Java: أداة JaCoCo، ويُضاف وكيلها (Java agent) عند بدء تشغيل التطبيق، فيستطيع أيضاً تسجيل التغطية أثناء اختبار خادم اختباراً يدوياً.
- في خط التكامل المستمر (CI) تنشر هذه الأدوات التقرير، ويمكنها إفشال البناء إذا نزلت التغطية عن حدّ معيّن، مثل
coverageThresholdفي Jest أو--fail-underفي coverage.py.
تصميم الاختبارات انطلاقاً من الشيفرة غير المغطاة
نادراً ما يبدأ تصميم الصندوق الأبيض من صفحة فارغة: فالفرق تصمّم الاختبارات من المتطلبات، ثم تقيس تغطيتها، ثم تسدّ الثغرات. ولكل بند غير مغطى:
- اقرأ القرار الذي يحرسه: ما الذي يجب أن يكون صحيحاً أو خاطئاً للوصول إليه؟
- ارجع منه إلى مُدخل: أيّ قيم للمعاملات أو البيانات أو الحالة تحقق ذلك الشرط؟ والمقارنة حدٌّ بحد ذاتها، فيساعدك تحليل القيم الحدّية.
- خذ النتيجة المتوقعة من أساس الاختبار، لا من الشيفرة أبداً: فالاختبار المنسوخ من التنفيذ يؤكد كل ما تفعله الشيفرة، بما في ذلك عيوبها.
- إذا لم يصف المتطلب هذا السلوك، فقد وجدت ثغرة في أساس الاختبار: اسأل أولاً.
- أعد تشغيل الاختبارات: يجب أن يصير البند مغطى، وأن يتحقق الاختبار الجديد من النتيجة بتأكيد (Assertion).
- إذا لم يصل أي مُدخل إلى الشيفرة، فأبلغ عن شيفرة ميتة محتملة (Dead code) بدلاً من فرض التغطية.
- 1قِس تغطية الاختبارات المبنية على المتطلباتيُعلّم التقرير ما لم يُنفَّذ قط
- 2اقرأ القرار الذي يحرس البندما الذي يجب أن يكون صحيحاً أو خاطئاً للوصول إليه؟
- 3ارجع إلى مُدخلقيم المعاملات أو البيانات أو الحالة؛ والمقارنة حدٌّ بحد ذاتها
فرع: لا مُدخل يصل إليه: أبلغ عن شيفرة ميتة محتملةتنبيه
- 4النتيجة المتوقعة من أساس الاختبارلا تُنسخ من الشيفرة أبداً
فرع: المتطلب صامت: اسأل قبل كتابة الاختبارتنبيه
- 5أعد التشغيل وتحقّقالبند مغطى والنتيجة يُتحقق منها بتأكيد
عودة إلى الخطوة 2 البند غير المغطى التالي
مثال تطبيقي: السطر الذي لم يذكره أي متطلب
المتطلب: «يكسب العميل نقطة ولاء واحدة عن كل 10.00 ريالات كاملة من قيمة الطلب. ويكسب أعضاء الفئة الذهبية ضعف النقاط». وفي الأسفل دالة المطوّر، وثلاثة اختبارات من نوع الصندوق الأسود، وتقرير التغطية من تشغيل CI. والدالة Math.floor تقرّب إلى الأدنى، فطلب قيمته 125.00 ريالاً يكسب 12 نقطة.
السطر 10 لم يُنفَّذ قط. فهو يمنح 5 نقاط إضافية لطلبات التطبيق، وهي قاعدة لا يذكرها المتطلب. وبدلاً من نسخ +5 إلى نتيجة متوقعة، يسأل المختبِر مالك المنتج، فيؤكدها: كل طلب عبر التطبيق يكسب 5 نقاط إضافية، بما في ذلك الطلبات الأقل من 10.00 ريالات.
- الاختبار T4
loyaltyPoints(125, false, true)يتوقع 17 وينجح. وصارت تغطية التعليمات والفروع 100%. - لكن القاعدة المؤكدة تحتاج أيضاً إلى T5
loyaltyPoints(5, false, true)← 5. فتُعيد الشيفرة 0 لأن جملةreturnالمبكرة في السطر 3 تتخطى المكافأة: عيب على مسار لم تتطلبه تغطية الفروع الكاملة قط. - كشف السطر غير المغطى متطلباً مفقوداً، وكشفت الإجابة عيباً. ولم تُظهر النسبة المئوية أياً منهما.
1 function loyaltyPoints(total: number, gold: boolean, app: boolean) {
2 if (total < 10) {
3 return 0;
4 }
5 let points = Math.floor(total / 10);
6 if (gold) {
7 points = points * 2;
8 }
9 if (app) {
10 points = points + 5;
11 }
12 return points;
13 }
Black-box tests Expected Actual Result
T1 loyaltyPoints(5, false, false) 0 0 passed
T2 loyaltyPoints(125, false, false) 12 12 passed
T3 loyaltyPoints(125, true, false) 24 24 passed
Coverage (CI run) Statements Branches Not run
loyalty.ts 7/8 87.5% 5/6 83% line 10: if (app) never trueما لا تخبرك به التغطية
تناول درس تغطية الاختبار الأساسيات: تنفيذ الشيفرة ليس تحققاً منها، والشيفرة التي لم تُكتب قط لا يمكن أن تظهر غير مغطاة. وحين تصمّم الاختبارات انطلاقاً من التغطية، تذكّر أيضاً:
- صحة النتائج المتوقعة: قد يتحقق الاختبار من قيمة خاطئة، كقيمة منسوخة من الشيفرة، ويُحتسب مع ذلك.
- التركيبات: تغطية الفروع الكاملة تمرّ بكل قرار في الاتجاهين، لا بكل تركيبة. فقد بلغت الاختبارات من T1 إلى T4 نسبة 100% وفاتها «طلب صغير عبر التطبيق».
- العيوب المرتبطة بالبيانات: تعليمة نُفّذت ببيانات غير ضارة قد تُحدث إخفاقاً مع قيم أخرى، كمقسوم عليه يساوي صفراً.
- الشيفرة خارج التقرير: فأداة Jest مثلاً لا تحتسب إلا الملفات التي حمّلتها الاختبارات ما لم يُضبط
collectCoverageFrom؛ والمكتبات وإجراءات قاعدة البيانات والإعدادات والخدمات الأخرى لا تُقاس في العادة. - الجودة غير الوظيفية: الأداء والأمان وقابلية الاستخدام وإمكانية الوصول تحتاج إلى اختباراتها الخاصة.
أخطاء شائعة
- نسخ النتيجة المتوقعة من الشيفرة قيد الاختبار، فيؤكد الاختبار العيب بدلاً من كشفه.
- جعل نسبة التغطية هدفاً بحد ذاتها، وإضافة اختبارات لا تتحقق من شيء.
- اعتبار تغطية التعليمات 100% «اختباراً كاملاً» مع أن الفروع والتركيبات والحالات المرتبطة بالبيانات ما زالت غير مختبرة.
- تغطية فرع غير موثّق بصمت بدلاً من السؤال عن السلوك المطلوب.
- الاعتماد على اختبار الصندوق الأبيض وحده: فالمتطلبات المفقودة لا تترك شيفرة تُغطّى.
- قراءة الإجمالي فقط دون النظر في الملفات والأسطر غير المغطاة، أو في الملفات التي يشملها التقرير أصلاً.
من منظور ضمان الجودة
في معظم الفرق يملك المطورون اختبارات الوحدة، وتعمل أداة التغطية في خط CI مع كل تغيير. ويقرأ المختبِر التقرير والمخاطر حاضرة في ذهنه: يطلب الأسطر غير المغطاة في الشيفرة المتغيرة والحرجة، ويتحقق هل تخفي سلوكاً لا تصفه المتطلبات، ويراجع هل تتحقق الاختبارات التي تغطيها من النتائج الصحيحة. والمرور على الفروع غير المغطاة مع مطوّر هو أسرع طريق للبدء.
وحين تُبلغ عن التغطية، سمِّ المعيار وحزمة الاختبارات: «اختبارات الوحدة، تغطية الفروع للملفات المتغيرة: 92%، فرعان غير مغطيين في معالجة الاسترداد، وطُرحت أسئلة بشأنهما»، وتابع كل سؤال أثاره فرع غير مغطى، فإجابته واحدة من ثلاث: اختبار ناقص، أو متطلب ناقص، أو شيفرة ميتة.
تمرين عملي
سُدّ ثغرة في تغطية دالة قفل الحساب
المتطلب: «بعد 5 كلمات مرور خاطئة متتالية يُقفل الحساب. ولا يستطيع الحساب المقفل تسجيل الدخول حتى بكلمة المرور الصحيحة. وكلمة المرور الصحيحة تُعيد عدّاد المحاولات الخاطئة إلى الصفر». وفي الأسفل الدالة login واختبارات الوحدة الثلاثة الخاصة بها وتقرير التغطية.
حدّد القاعدة التي لا اختبار لها، وصمّم اختباراً يشغّل السطر غير المغطى بنتيجة متوقعة مأخوذة من المتطلب، وقرّر هل سينجح. ثم اكتب ما لن تخبرك به تغطية 100% حتى عندئذ.
1 function login(user: User, password: string): string {
2 if (user.locked) {
3 return 'LOCKED';
4 }
5 if (checkPassword(user, password)) {
6 user.failedAttempts = 0;
7 return 'OK';
8 }
9 user.failedAttempts = user.failedAttempts + 1;
10 if (user.failedAttempts > 5) {
11 user.locked = true;
12 }
13 return 'WRONG_PASSWORD';
14 }
Unit tests Expected Result
T1 correct password, failedAttempts = 2 'OK' passed
T2 wrong password, failedAttempts = 0 'WRONG_PASSWORD' passed
T3 correct password, user.locked = true 'LOCKED' passed
Coverage Statements Branches Not run
login.ts 8/9 89% 5/6 83% line 11- اربط كل جملة من المتطلب بالاختبارات T1 وT2 وT3.
- حدّد الشرط الذي يحرس السطر 11 والحالة التي تجعله صحيحاً.
- اكتب الاختبار الجديد وجاره الحدّي، ثم تتبّعهما عبر الشيفرة: هل ينجحان أم يفشلان؟
- اكتب ما يبقى مجهولاً عند تغطية تعليمات وفروع بنسبة 100%.
إظهار الإجابة النموذجية
يغطي T3 الجملة الثانية: «لا يستطيع الحساب المقفل تسجيل الدخول». ويشغّل T1 إعادة العدّاد إلى الصفر في السطر 6 (الجملة الثالثة) إلى جانب تسجيل دخول ناجح، ويعدّ T2 كلمة مرور خاطئة واحدة. ولا شيء يغطي الجملة الأولى، أي القفل بعد 5 كلمات مرور خاطئة: فهذه القاعدة هي السطران 10 و11، والسطر 11 لم يُنفَّذ قط.
يحتاج السطر 11 إلى أن يكون user.failedAttempts > 5 صحيحاً بعد الزيادة في السطر 9. وكلمة المرور الخاطئة الخامسة يجب أن تُقفل الحساب، فالاختبار هو: failedAttempts = 4، وكلمة مرور خاطئة، والمتوقع user.locked === true. ولا تتحقق من 'WRONG_PASSWORD' في هذه المحاولة لمجرد أن الشيفرة تعيدها: فالمتطلب لا يذكر ما تعيده المحاولة التي تُقفل الحساب، فاسأل عنه. وجاره الحدّي: failedAttempts = 3، وكلمة مرور خاطئة، ويبقى الحساب غير مقفل.
ينجح الجار الحدّي، لكن الاختبار الجديد يفشل: 4 + 1 = 5 والشرط 5 > 5 خاطئ، فلا يُقفل الحساب إلا بعد كلمة المرور الخاطئة السادسة. أبلغ عن عيب الفارق بواحد هذا مع المُدخل والنتيجتين المتوقعة والفعلية والسطر 10؛ والإصلاح هو >= 5.
الاختبار المنسوخ من الشيفرة (failedAttempts = 5، والمتوقع حساب مقفل) كان سيبلغ تغطية 100% وينجح، فيؤكد العيب.
ما يبقى مجهولاً عند 100%: هل يتحقق T1 من أن failedAttempts عاد إلى الصفر، وهل يبقى العدّ صحيحاً عبر الجلسات والأجهزة والطلبات المتزامنة، وهل الدالة checkPassword صحيحة، وكيف يُفتح الحساب المقفل، وهو أمر لا يصفه المتطلب ولا الشيفرة: سؤال لمالك المنتج.
أسئلة شائعة
هل يجب أن أكون مبرمجاً لأُجري اختبار الصندوق الأبيض؟
تحتاج إلى قراءة الشيفرة بما يكفي لتتبّع قراراتها: الشروط والحلقات وجمل return المبكرة. ولا تحتاج إلى كتابة شيفرة الإنتاج. ويبدأ كثير من المختبِرين بقراءة تقارير التغطية ومراجعة اختبارات الوحدة مع مطوّر، ثم يتقدمون من هناك.
هل اختبار الصندوق الأبيض هو اختبار الوحدة نفسه؟
لا. اختبار الوحدة (المكوّن) مستوى اختبار، أما الصندوق الأبيض فطريقة لاشتقاق الاختبارات وقياسها. وتُستخدم تقنياته أكثر ما تُستخدم على مستوى المكوّن، لكنها تنطبق على اختبار التكامل والنظام أيضاً، وكثيراً ما تُصمَّم اختبارات الوحدة نفسها بأسلوب الصندوق الأسود من مواصفات المكوّن.
هل تغطية الأسطر هي تغطية التعليمات نفسها؟
قريبة منها لكنها ليست مطابقة. فالسطر قد يحوي عدة تعليمات أو قراراً كاملاً مثل a && b ? x : y (أي x إذا كان a && b صحيحاً، وإلا y)، فيُحتسب السطر مغطى رغم أن جزءاً منه لم يُنفَّذ. وأرقام الفروع وعلامات الأداة للأسطر المغطاة جزئياً تُظهر ما تخفيه تغطية الأسطر.
هل يمكن قياس التغطية للاختبارات اليدوية؟
نعم، إذا شُغّل التطبيق بنسخة مجهّزة بعدّادات أو مع وكيل تغطية، مثل وكيل JaCoCo لخادم Java. وعندها يُظهر التقرير الشيفرة التي وصلت إليها جلسة يدوية أو استكشافية. وهذا يساعد على إيجاد المناطق التي لم يلمسها أحد، مع الحدّ المعتاد نفسه: لا يستطيع أن يخبرك هل لاحظت نتيجة خاطئة.
المراجع
- ISTQB Certified Tester Foundation Level (CTFL) v4.0المنهج التأسيسي لاختبار البرمجيات: مستويات الاختبار وأنواعه وتقنيات تصميم الاختبارات وإدارة الاختبار والأدوات.
- ISTQB Glossaryالتعريفات القياسية لمصطلحات الاختبار — حالة الاختبار واختبار الانحدار والخطورة والأولوية وتحليل القيم الحدّية وغيرها.
- ISO/IEC/IEEE 29119-1:2022 — Software and systems engineering — Software testing — Part 1: General conceptsالمعيار الدولي لاختبار البرمجيات: المفاهيم والعمليات والتوثيق والتقنيات.
- NIST SP 500-235: Structured Testing: A Testing Methodology Using the Cyclomatic Complexity Metricالاختبار المهيكل (اختبار المسارات الأساسية): استخدام مقياس التعقيد الدوري لـ McCabe لاشتقاق مجموعة أساسية من المسارات المستقلة لاختبارها — وهو أشمل من تغطية التعليمات والفروع.
تدرّب بأدوات متقن
أنشئ جداول القرار من الشروط والإجراءات، وولّد جميع تركيبات القواعد، واختزل المستحيل منها.
ولّد القيم الحدّية (min-1 وmin وmin+1 والقيمة الاسمية وmax-1 وmax وmax+1) للقيود الرقمية وقيود الطول.
حوّل قائمة متطلبات مرقّمة إلى حالات اختبار قابلة للتتبّع مع مصفوفة تربط المتطلبات بالاختبارات.
للتعمّق أكثر
تقدّمك في هذا الموضوع
سجّل الدخول لتتابع تقدّمك
علّم الموضوعات مكتملة، وتابع من حيث توقفت، وافتح امتحانات التخصص.