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

PyTest وأسلوب assert

اختبارٌ بأقلّ طقوس: دالةٌ و assert، وإطارٌ يفهم الباقي. ⏱ نحو 40 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تكتب اختبارات بأسلوب PyTest.
  • تقارن بين unittest و PyTest.
  • تفهم Fixtures كمكافئ setUp.
  • تختار الإطار المناسب لفريقك.

١اختبارٌ بلا طقوس

unittest قويّ لكنه مطوّل: أصناف، وراثة، self.assertEqual. أشهر إطارٍ في مجتمع بايثون اليوم هو PyTest، وفلسفته: اكتب دوالًا عادية بـ assert عاديّ، وهو يكتشفها ويشغّلها ويحلّل الفشل بذكاء.

unittestPyTest
الصنفيرث TestCaseلا حاجة — دالة فقط
التأكيدself.assertEqual(a, b)assert a == b
الاكتشافدوال test_ في أصنافدوال test_ في أيّ ملف

السرّ: PyTest «يُعيد كتابة» assert ليعرض الفعلي والمتوقَّع عند الفشل، فتجمع بين بساطة assert ووضوح تقارير الإطار.

٢أسلوب PyTest — نشغّله بمجمِّع بسيط

PyTest نفسه أداةُ سطر أوامر، لكن أسلوبه (دوال test_ بـ assert) نحاكيه هنا بمجمِّعٍ صغير يجد الدوال ويشغّلها — تمامًا كما يفعل PyTest:

main.py
def normalize_email(e):    return e.strip().lower() # أسلوب PyTest: دوال عادية بـ assert عاديّdef test_lowercase():    assert normalize_email("A@B.SA") == "a@b.sa" def test_trim():    assert normalize_email("  x@y.sa ") == "x@y.sa" def test_already_clean():    assert normalize_email("ok@site.sa") == "ok@site.sa" # مجمِّع بسيط (PyTest يفعل هذا تلقائيًّا من سطر الأوامر)import sysmod = sys.modules[__name__]tests = [v for k, v in globals().items() if k.startswith("test_") and callable(v)]passed = 0for t in tests:    try:        t(); passed += 1; print("PASS", t.__name__)    except AssertionError:        print("FAIL", t.__name__)print(f"= {passed}/{len(tests)} نجحت =")
Shell×
>>> %Run main.py
PASS test_lowercase
PASS test_trim
PASS test_already_clean
= 3/3 نجحت =
>>>

٣التجهيزات Fixtures

مكافئ setUp في PyTest هو Fixture: دالةٌ تُحضّر بيانات الاختبار، ويطلبها الاختبار بذكر اسمها معاملًا. في العمل الحقيقيّ:

test_cart.py
# ملف test_cart.py — يُشغَّل بالأمر:  pytestimport pytest @pytest.fixturedef cart():    return Cart()          # تُحضَّر قبل كل اختبار يطلبها def test_empty(cart):      # PyTest يحقن القيمة تلقائيًّا    assert cart.total() == 0 def test_add(cart):    cart.add("سماعة")    assert cart.total() == 1

توقّع وتحقّق

فلسفة PyTest في التأكيد:

PyTest يكتشف الدوال التي تبدأ بـ:

مكافئ setUp في PyTest:

ميزة PyTest في الفشل:

الخلاصة

تحدٍّ برمجي: أسلوب PyTest

متوسط

اكتب دالتي اختبار بأسلوب PyTest (دالة + assert) لدالة slugify التي تحوّل النصّ لحروفٍ صغيرة بشرطة بدل الفراغ، ثم شغّلهما بالمجمِّع المعطى.

المطلوب:
  • دالتان تبدآن بـ test_ فيهما assert.
  • اختبر "Hello World" → "hello-world" و"A B" → "a-b".

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

main.py
def slugify(s):    return s.strip().lower().replace(" ", "-")# اكتب test_basic و test_two بـ assertdef run():    import sys    tests = [v for k, v in globals().items() if k.startswith("test_") and callable(v)]    p = 0    for t in tests:        try:            t(); p += 1        except AssertionError:            print("FAIL", t.__name__)    print(p, "/", len(tests))run()
المطلوب×
2 / 2
💡 تلميح 1
def test_basic(): assert slugify("Hello World") == "hello-world".
💡 تلميح 2
عرّف الدالتين قبل run().

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

main.py
def slugify(s):    return s.strip().lower().replace(" ", "-")def test_basic():    assert slugify("Hello World") == "hello-world"def test_two():    assert slugify("A B") == "a-b"def run():    import sys    tests = [v for k, v in globals().items() if k.startswith("test_") and callable(v)]    p = 0    for t in tests:        try:            t(); p += 1        except AssertionError:            print("FAIL", t.__name__)    print(p, "/", len(tests))run()
انتقل إلى تقييم الدرساختيار من متعدد، وصح وخطأ، وأكمل الفراغ، وتتبّع الناتج.ابدأ التقييم ←

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