التطوير الموجَّه بالاختبار TDD
ماذا يحدث حين تكتب الاختبار قبل الكود؟ ينقلب تفكيرك كلّه. ⏱ نحو 45 دقيقة.
- تكتب الاختبار قبل الكود.
- تتبع دورة أحمر-أخضر-إعادة هيكلة.
- تمشي بخطواتٍ صغيرة.
- تدرك فوائد
TDDعلى التصميم والتغطية.
١اكتب الاختبار أوّلًا
حتى الآن اختبرنا كودًا موجودًا. التطوير الموجَّه بالاختبار TDD يقلب الترتيب: تكتب الاختبار قبل الكود. يبدو غريبًا، لكنه يغيّر طريقة تفكيرك: تحدّد ماذا يجب أن تفعل الدالة قبل أن تشغل بالك بـ«كيف».
| الخطوة | اللون | المعنى |
|---|---|---|
| 1) اكتب اختبارًا يفشل | 🔴 أحمر | الدالة لم تُكتَب بعد، فالاختبار يفشل |
| 2) اكتب أقلّ كودٍ ينجّحه | 🟢 أخضر | أبسط حلٍّ يمرّ الاختبار |
| 3) حسّن الكود | 🔵 إعادة هيكلة | نظّف مع بقاء الأخضر |
الدورة أحمر ← أخضر ← إعادة هيكلة، خطوةً صغيرة في كل مرّة. النتيجة: كلّ سطرٍ مكتوبٌ لأن اختبارًا طلبه، فالتغطية كاملةٌ بطبيعتها، والتصميم أوضح.
٢دورة TDD كاملة
لنبنِ دالة fizzbuzz بالـ TDD. أحمر أوّلًا (الاختبار يفشل لأن الدالة ناقصة)، ثم أخضر:
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))
في TDD الحقيقيّ كتبنا test_number أوّلًا (أحمر)، ثم أبسط كودٍ ينجّحه، ثم test_fizz، وهكذا. كل اختبارٍ دفع الكود خطوة.
٣لماذا يعمل TDD؟
| الفائدة | كيف |
|---|---|
| تصميم أوضح | تفكّر في الواجهة (ماذا) قبل التنفيذ (كيف) |
| تغطية كاملة | كل كودٍ كُتب لينجّح اختبارًا |
| ثقة في التغيير | شبكة أمانٍ تكشف أيّ كسرٍ فورًا |
| توثيق حيّ | الاختبارات تصف ما يفعله الكود |
توقّع وتحقّق
في TDD نكتب أوّلًا:
دورة TDD:
«أحمر» يعني:
حجم خطوة TDD:
الخلاصة
TDDيقلب الترتيب: اكتب الاختبار الفاشل أوّلًا، ثم الكود الذي ينجّحه.- الدورة: أحمر (اختبار يفشل) ← أخضر (أبسط كودٍ يمرّ) ← إعادة هيكلة.
- خطواتٌ صغيرة جدًّا؛ لا تكتب كودًا لا يطلبه اختبار.
- النتيجة: تصميم أوضح، تغطية كاملة، وثقةٌ في التغيير، وتوثيقٌ حيّ.
تحدٍّ برمجي: أخضر بعد أحمر
متقدمكُتب اختبارٌ يفشل (أحمر) لدالة is_palindrome غير المكتملة. اكتب أقلّ كودٍ ينجّح الاختبارات، ثم شغّلها.
- أكمل
is_palindromeلتعيد True إن قُرئ النصّ كما يُعكَس. - شغّل الاختبارات؛ يجب أن تصير كلّها خضراء.
ابدأ من هذا الكود، واضغط ▶ شغّل هنا لتكتب حلّك وتجرّبه مباشرة:
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))
💡 تلميح 1
s[::-1].💡 تلميح 2
return s == s[::-1].حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.
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))