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

اختبار الاستثناءات والأخطاء

حين يكون الفشل هو النجاح: كيف نختبر أن الدالة ترفض ما يجب رفضه؟ ⏱ نحو 40 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تختبر أن الدالة ترفض ما يجب رفضه.
  • تستخدم assertRaises للتحقّق من الاستثناءات.
  • تتحقّق من نوع الخطأ الصحيح.
  • تتجنّب التقاط Exception العامّ.

١السلوك الصحيح قد يكون رفضًا

ليست كل الاختبارات تتحقّق من نتيجةٍ ناجحة. أحيانًا السلوك الصحيح هو رفع خطأ: القسمة على صفر يجب أن تفشل، وسحب مبلغٍ أكبر من الرصيد يجب أن يُرفَض. اختبار أن الدالة ترفض ما يجب رفضه لا يقلّ أهمّية عن اختبار نجاحها.

main.py
def withdraw(balance, amount):    if amount > balance:        raise ValueError("الرصيد لا يكفي")    return balance - amount # نتحقّق أن السحب الزائد يرفع الخطأ المتوقَّعtry:    withdraw(100, 500)    print("✗ فشل الاختبار: كان يجب أن يُرفَع خطأ")except ValueError:    print("✓ نجح: رُفِض السحب الزائد كما يجب")
Shell×
>>> %Run main.py
✓ نجح: رُفِض السحب الزائد كما يجب
>>>

غياب الخطأ هنا عيب. لو سمحت الدالة بسحبٍ زائد لمرّت بصمت وكلّفت المتجر مالًا. الاختبار يتأكّد أن الحارس يعمل.

٢assertRaises: الطريقة النظيفة

كتابة try/except لكل اختبار خطأٍ مُطوِّلة. إطار unittest يقدّم assertRaises بوصفه مديرَ سياق أنيق:

main.py
import unittest def parse_qty(text):    n = int(text)              # يرفع ValueError إن لم يكن رقمًا    if n < 0:        raise ValueError("الكمية سالبة")    return n class TestParseQty(unittest.TestCase):    def test_valid(self):        self.assertEqual(parse_qty("5"), 5)    def test_not_number(self):        with self.assertRaises(ValueError):            parse_qty("abc")    def test_negative(self):        with self.assertRaises(ValueError):            parse_qty("-3") 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
>>>

with self.assertRaises(ValueError): يقول «أتوقّع أن يُرفَع هذا الخطأ هنا». إن لم يُرفَع، فشل الاختبار. سنتوسّع في unittest في الدرس القادم.

٣النوع الصحيح من الخطأ

لا يكفي أن يفشل؛ يجب أن يفشل بالطريقة الصحيحة. اختبر أن نوع الاستثناء هو المتوقَّع، فقد يخفي خطأٌ عامٌّ عيبًا:

main.py
def get_item(cart, index):    return cart[index]          # يرفع IndexError إن تجاوز cart = ["سماعة", "كاميرا"]tests = 0# المتوقَّع: IndexError لا أيّ خطأtry:    get_item(cart, 5)except IndexError:    tests += 1; print("✓ رفع IndexError كما يجب")except Exception as e:    print("✗ رفع نوعًا خاطئًا:", type(e).__name__)print("نجح", tests, "من 1")
Shell×
>>> %Run main.py
✓ رفع IndexError كما يجب
نجح 1 من 1
>>>

توقّع وتحقّق

أحيانًا السلوك الصحيح هو:

للتحقّق من رفع خطأ في unittest:

التقاط Exception العامّ:

إن لم يُرفَع الخطأ المتوقَّع:

الخلاصة

تحدٍّ برمجي: اختبر الرفض

متوسط

الدالة safe_div ترفع ZeroDivisionError عند القسمة على صفر. تحقّق أنها ترفع الخطأ، واطبع رسالة نجاح؛ وتحقّق أن safe_div(10,2)==5.

المطلوب:
  • تأكّد أن safe_div(1, 0) يرفع ZeroDivisionError.
  • اطبع «نجح» عند الرفض، ثم اطبع ناتج safe_div(10, 2).

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

main.py
def safe_div(a, b):    return a / b# تحقّق أن safe_div(1,0) يرفع ZeroDivisionError، اطبع "نجح"، ثم اطبع safe_div(10,2)
المطلوب×
نجح
5.0
💡 تلميح 1
استخدم try/except ZeroDivisionError.
💡 تلميح 2
في except اطبع "نجح"، ثم اطبع safe_div(10, 2).

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

main.py
def safe_div(a, b):    return a / btry:    safe_div(1, 0)    print("فشل")except ZeroDivisionError:    print("نجح")print(safe_div(10, 2))
انتقل إلى تقييم الدرساختيار من متعدد، وصح وخطأ، وأكمل الفراغ، وتتبّع الناتج.ابدأ التقييم ←

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