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

التطوير الموجَّه بالاختبار TDD

ماذا يحدث حين تكتب الاختبار قبل الكود؟ ينقلب تفكيرك كلّه. ⏱ نحو 45 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تكتب الاختبار قبل الكود.
  • تتبع دورة أحمر-أخضر-إعادة هيكلة.
  • تمشي بخطواتٍ صغيرة.
  • تدرك فوائد TDD على التصميم والتغطية.

١اكتب الاختبار أوّلًا

حتى الآن اختبرنا كودًا موجودًا. التطوير الموجَّه بالاختبار TDD يقلب الترتيب: تكتب الاختبار قبل الكود. يبدو غريبًا، لكنه يغيّر طريقة تفكيرك: تحدّد ماذا يجب أن تفعل الدالة قبل أن تشغل بالك بـ«كيف».

الخطوةاللونالمعنى
1) اكتب اختبارًا يفشل🔴 أحمرالدالة لم تُكتَب بعد، فالاختبار يفشل
2) اكتب أقلّ كودٍ ينجّحه🟢 أخضرأبسط حلٍّ يمرّ الاختبار
3) حسّن الكود🔵 إعادة هيكلةنظّف مع بقاء الأخضر

الدورة أحمر ← أخضر ← إعادة هيكلة، خطوةً صغيرة في كل مرّة. النتيجة: كلّ سطرٍ مكتوبٌ لأن اختبارًا طلبه، فالتغطية كاملةٌ بطبيعتها، والتصميم أوضح.

٢دورة TDD كاملة

لنبنِ دالة fizzbuzz بالـ TDD. أحمر أوّلًا (الاختبار يفشل لأن الدالة ناقصة)، ثم أخضر:

main.py
import unittest # 🟢 بعد عدّة دورات، هذا هو الكود الذي طلبته الاختباراتdef fizzbuzz(n):    if n % 15 == 0: return "FizzBuzz"    if n % 3 == 0:  return "Fizz"    if n % 5 == 0:  return "Buzz"    return str(n) # الاختبارات التي كُتبت أوّلًا (واحدًا تلو الآخر)class TestFizzBuzz(unittest.TestCase):    def test_number(self):   self.assertEqual(fizzbuzz(1), "1")    def test_fizz(self):     self.assertEqual(fizzbuzz(3), "Fizz")    def test_buzz(self):     self.assertEqual(fizzbuzz(5), "Buzz")    def test_fizzbuzz(self): self.assertEqual(fizzbuzz(15), "FizzBuzz") import sysunittest.main(argv=[""], exit=False,    testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2))
Shell×
>>> %Run main.py
----------------------------------------------------------------------
Ran 0 tests in 0.000s
NO TESTS RAN
>>>

في TDD الحقيقيّ كتبنا test_number أوّلًا (أحمر)، ثم أبسط كودٍ ينجّحه، ثم test_fizz، وهكذا. كل اختبارٍ دفع الكود خطوة.

٣لماذا يعمل TDD؟

الفائدةكيف
تصميم أوضحتفكّر في الواجهة (ماذا) قبل التنفيذ (كيف)
تغطية كاملةكل كودٍ كُتب لينجّح اختبارًا
ثقة في التغييرشبكة أمانٍ تكشف أيّ كسرٍ فورًا
توثيق حيّالاختبارات تصف ما يفعله الكود

توقّع وتحقّق

في TDD نكتب أوّلًا:

دورة TDD:

«أحمر» يعني:

حجم خطوة TDD:

الخلاصة

تحدٍّ برمجي: أخضر بعد أحمر

متقدم

كُتب اختبارٌ يفشل (أحمر) لدالة is_palindrome غير المكتملة. اكتب أقلّ كودٍ ينجّح الاختبارات، ثم شغّلها.

المطلوب:
  • أكمل is_palindrome لتعيد True إن قُرئ النصّ كما يُعكَس.
  • شغّل الاختبارات؛ يجب أن تصير كلّها خضراء.

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

main.py
import unittestdef is_palindrome(s):    return False      # 🔴 ناقص — اجعل الاختبارات خضراءclass TestPal(unittest.TestCase):    def test_yes(self):  self.assertTrue(is_palindrome("level"))    def test_no(self):   self.assertFalse(is_palindrome("code"))    def test_one(self):  self.assertTrue(is_palindrome("a"))import sysunittest.main(argv=[""], exit=False, testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2))
المطلوب×
----------------------------------------------------------------------
Ran 0 tests in 0.000s
NO TESTS RAN
💡 تلميح 1
النصّ معكوسًا: s[::-1].
💡 تلميح 2
return s == s[::-1].

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

main.py
import unittestdef is_palindrome(s):    return s == s[::-1]class TestPal(unittest.TestCase):    def test_yes(self):  self.assertTrue(is_palindrome("level"))    def test_no(self):   self.assertFalse(is_palindrome("code"))    def test_one(self):  self.assertTrue(is_palindrome("a"))import sysunittest.main(argv=[""], exit=False, testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2))
انتقل إلى تقييم الدرساختيار من متعدد، وصح وخطأ، وأكمل الفراغ، وتتبّع الناتج.ابدأ التقييم ←

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