PyTest وأسلوب assert
اختبارٌ بأقلّ طقوس: دالةٌ و assert، وإطارٌ يفهم الباقي. ⏱ نحو 40 دقيقة.
- تكتب اختبارات بأسلوب
PyTest. - تقارن بين
unittestوPyTest. - تفهم
FixturesكمكافئsetUp. - تختار الإطار المناسب لفريقك.
١اختبارٌ بلا طقوس
unittest قويّ لكنه مطوّل: أصناف، وراثة، self.assertEqual. أشهر إطارٍ في مجتمع بايثون اليوم هو PyTest، وفلسفته: اكتب دوالًا عادية بـ assert عاديّ، وهو يكتشفها ويشغّلها ويحلّل الفشل بذكاء.
| unittest | PyTest | |
|---|---|---|
| الصنف | يرث TestCase | لا حاجة — دالة فقط |
| التأكيد | self.assertEqual(a, b) | assert a == b |
| الاكتشاف | دوال test_ في أصناف | دوال test_ في أيّ ملف |
السرّ: PyTest «يُعيد كتابة» assert ليعرض الفعلي والمتوقَّع عند الفشل، فتجمع بين بساطة assert ووضوح تقارير الإطار.
٢أسلوب PyTest — نشغّله بمجمِّع بسيط
PyTest نفسه أداةُ سطر أوامر، لكن أسلوبه (دوال test_ بـ assert) نحاكيه هنا بمجمِّعٍ صغير يجد الدوال ويشغّلها — تمامًا كما يفعل PyTest:
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)} نجحت =")
٣التجهيزات Fixtures
مكافئ setUp في PyTest هو Fixture: دالةٌ تُحضّر بيانات الاختبار، ويطلبها الاختبار بذكر اسمها معاملًا. في العمل الحقيقيّ:
# ملف 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أشهر أطر بايثون: دوال عادية بـassertعاديّ، بلا أصناف ولا وراثة.- يكتشف دوال
test_تلقائيًّا، ويعرض الفعلي والمتوقَّع عند الفشل. FixturesمكافئsetUp: تُحضّر بيانات الاختبار وتُحقَن بذكر اسمها.unittestوPyTestكلاهما صحيح؛ المبدأ واحد والاختيار حسب الفريق.
تحدٍّ برمجي: أسلوب PyTest
متوسطاكتب دالتي اختبار بأسلوب PyTest (دالة + assert) لدالة slugify التي تحوّل النصّ لحروفٍ صغيرة بشرطة بدل الفراغ، ثم شغّلهما بالمجمِّع المعطى.
- دالتان تبدآن بـ
test_فيهماassert. - اختبر
"Hello World" → "hello-world"و"A B" → "a-b".
ابدأ من هذا الكود، واضغط ▶ شغّل هنا لتكتب حلّك وتجرّبه مباشرة:
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()
💡 تلميح 1
def test_basic(): assert slugify("Hello World") == "hello-world".💡 تلميح 2
run().حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.
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()