طَبِّقْهاتعلّم المبدأ، وطبّقه بيدكمقرر 245 · ضمان جودة تطبيقات الويب
الدرس 9 · الوحدة 5: التوثيق وتقارير النتائج

الاختبارات المَعلَمة

عشر حالاتٍ باختبارٍ واحد نظيف، وكل فشلٍ يُبلَّغ بدقّة. ⏱ نحو 40 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تكتب اختبارًا مَعلَمًا بجدول حالات.
  • تستخدم subTest لتبليغ كل فشل.
  • تميّز متى تعلّم ومتى تفصل.
  • تعرف مكافئ pytest.

١اختبارٌ واحد، حالاتٌ كثيرة

لدينا دالةٌ نريد اختبارها على عشر حالات. أنكتب عشر دوال test_ متطابقة إلا في الأرقام؟ تكرارٌ ممل. الحلّ: اختبار مَعلَم Parametrized — جدول حالاتٍ نمرّ عليه، وكل حالةٍ تُبلَّغ منفصلة بـ subTest:

main.py
import unittest def classify(score):    if score >= 90: return "ممتاز"    if score >= 60: return "ناجح"    return "راسب" class TestClassify(unittest.TestCase):    def test_all(self):        cases = [(95, "ممتاز"), (90, "ممتاز"), (75, "ناجح"),                 (60, "ناجح"), (59, "راسب"), (0, "راسب")]        for score, expected in cases:            with self.subTest(score=score):                self.assertEqual(classify(score), expected) import sysrunner = unittest.TextTestRunner(stream=sys.stdout, verbosity=2)unittest.main(argv=[""], exit=False, testRunner=runner)
Shell×
>>> %Run main.py
----------------------------------------------------------------------
Ran 0 tests in 0.000s
NO TESTS RAN
>>>

ميزة subTest: لو فشلت حالةٌ، يستمرّ الإطار ويبلّغ كل الحالات الفاشلة، لا الأولى فقط. ويذكر قيمة score التي فشلت، فتعرف أيّها بالضبط.

٢قوّة subTest: كشف كل الأعطال

انظر دالةً فيها عيبٌ عند حدٍّ واحد، وكيف يحدّد الاختبار المَعلَم الحالة الفاشلة بدقّة:

main.py
import unittest def is_adult(age):    return age > 18          # ✗ عيب: يجب >= 18 class TestAdult(unittest.TestCase):    def test_boundary(self):        cases = [(17, False), (18, True), (19, True), (25, True)]        for age, expected in cases:            with self.subTest(age=age):                self.assertEqual(is_adult(age), expected) import sysrunner = unittest.TextTestRunner(stream=sys.stdout, verbosity=2)unittest.main(argv=[""], exit=False, testRunner=runner)
Shell×
>>> %Run main.py
----------------------------------------------------------------------
Ran 0 tests in 0.000s
NO TESTS RAN
>>>

التقرير يشير إلى (age=18): الحالة الوحيدة الفاشلة. لولا subTest لعرفنا أن «شيئًا ما» فشل دون تحديده. هذا يسرّع التشخيص كثيرًا.

٣متى نعلّم الاختبار؟

في العمل الحقيقيّ تقدّم أطرٌ مثل pytest تعليمًا أوجز بـ @pytest.mark.parametrize، لكن الفكرة واحدة: جدولُ (مدخل، متوقَّع) يُشغَّل آليًّا.

توقّع وتحقّق

الاختبار المَعلَم يفيد حين:

subTest يفيد في:

بلا subTest، حلقة الاختبار:

في pytest التعليم بـ:

الخلاصة

تحدٍّ برمجي: اختبار مَعلَم

متوسط

اكتب اختبارًا مَعلَمًا لدالة sign (تعيد "+", "-", "0") على الحالات المعطاة باستخدام subTest، ثم شغّله.

المطلوب:
  • مرّ على الحالات داخل with self.subTest(...).
  • استخدم assertEqual، ثم شغّل.

ابدأ من هذا الكود، واضغط ▶ شغّل هنا لتكتب حلّك وتجرّبه مباشرة:

main.py
import unittestdef sign(n):    if n > 0: return "+"    if n < 0: return "-"    return "0"# اكتب اختبارًا مَعلَمًا على: (5,"+"),(-3,"-"),(0,"0") ثم شغّلهimport sys
المطلوب×
----------------------------------------------------------------------
Ran 0 tests in 0.000s
NO TESTS RAN
💡 تلميح 1
for n, expected in [...]: with self.subTest(n=n):
💡 تلميح 2
self.assertEqual(sign(n), expected).

حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.

main.py
import unittestdef sign(n):    if n > 0: return "+"    if n < 0: return "-"    return "0"class TestSign(unittest.TestCase):    def test_all(self):        for n, expected in [(5, "+"), (-3, "-"), (0, "0")]:            with self.subTest(n=n):                self.assertEqual(sign(n), expected)import sysrunner = unittest.TextTestRunner(stream=sys.stdout, verbosity=2)unittest.main(argv=[""], exit=False, testRunner=runner)
انتقل إلى تقييم الدرساختيار من متعدد، وصح وخطأ، وأكمل الفراغ، وتتبّع الناتج.ابدأ التقييم ←

📝 معلم؟ أرسل ملاحظة للمؤلف عن هذا الدرس