الاختبارات و TDD

التطوير الموجه بالاختبار (TDD)

22 دقيقة الدرس 3 من 35

التطوير الموجه بالاختبار (TDD)

التطوير الموجه بالاختبار (TDD) هو منهجية تطوير برمجيات حيث تكتب الاختبارات قبل كتابة الكود الفعلي. يقلب هذا النهج عملية التطوير التقليدية وله تأثيرات عميقة على جودة الكود والتصميم والثقة.

ما هو TDD؟

TDD هو نهج منضبط لتطوير البرمجيات يتبع دورة بسيطة:

  1. اكتب اختبارًا فاشلًا يصف السلوك الذي تريده
  2. اكتب ما يكفي من الكود لجعل الاختبار ينجح
  3. أعد هيكلة الكود لتحسين الجودة مع الحفاظ على نجاح الاختبارات

اقتباس Kent Beck: "التطوير الموجه بالاختبار هو طريقة لإدارة الخوف أثناء البرمجة." — يوفر الثقة بأن الكود الخاص بك يعمل وسيستمر في العمل.

دورة الأحمر-الأخضر-إعادة الهيكلة

قلب TDD هو دورة الأحمر-الأخضر-إعادة الهيكلة. دعنا نحلل كل مرحلة:

🔴 الأحمر: اكتب اختبارًا فاشلًا

اكتب اختبارًا للجزء التالي من الوظائف التي تريد إضافتها. سيفشل الاختبار لأن الوظيفة غير موجودة بعد.

<script> // الخطوة 1: اكتب اختبارًا فاشلًا test('should calculate total price with tax', () => { const cart = new ShoppingCart(); cart.addItem({ price: 100 }); const total = cart.getTotalWithTax(0.10); // 10% ضريبة expect(total).toBe(110); }); // ❌ الاختبار يفشل: getTotalWithTax غير معرف </script>

🟢 الأخضر: اجعل الاختبار ينجح

اكتب أبسط كود ممكن لجعل الاختبار ينجح. لا تقلق بشأن الكمال—فقط اجعله يعمل.

<script> // الخطوة 2: اكتب الحد الأدنى من الكود لتمرير الاختبار class ShoppingCart { constructor() { this.items = []; } addItem(item) { this.items.push(item); } getTotalWithTax(taxRate) { const subtotal = this.items.reduce((sum, item) => sum + item.price, 0); return subtotal + (subtotal * taxRate); } } // ✅ الاختبار ينجح! </script>

🔵 إعادة الهيكلة: حسّن الكود

الآن بعد أن نجحت الاختبارات، حسّن جودة الكود دون تغيير السلوك. تضمن الاختبارات عدم كسر أي شيء.

<script> // الخطوة 3: إعادة الهيكلة لجودة أفضل class ShoppingCart { constructor() { this.items = []; } addItem(item) { this.items.push(item); } getSubtotal() { return this.items.reduce((sum, item) => sum + item.price, 0); } calculateTax(taxRate) { return this.getSubtotal() * taxRate; } getTotalWithTax(taxRate) { return this.getSubtotal() + this.calculateTax(taxRate); } } // ✅ الاختبارات لا تزال تنجح، الكود أنظف! </script>

القاعدة الذهبية: لا تكتب كودًا جديدًا أبدًا ما لم يكن لديك اختبار فاشل. لا تعيد الهيكلة أبدًا بدون اختبارات ناجحة.

TDD في الممارسة: مثال كامل

دعنا نبني مدقق كلمات مرور باستخدام TDD من الصفر:

التكرار 1: كلمة مرور فارغة

<script> // 🔴 الأحمر: اكتب اختبارًا فاشلًا test('should reject empty password', () => { const validator = new PasswordValidator(); const result = validator.validate(''); expect(result.isValid).toBe(false); expect(result.errors).toContain('Password cannot be empty'); }); // ❌ PasswordValidator غير معرف </script>
<script> // 🟢 الأخضر: اجعله ينجح class PasswordValidator { validate(password) { if (!password) { return { isValid: false, errors: ['Password cannot be empty'] }; } return { isValid: true, errors: [] }; } } // ✅ الاختبار ينجح! </script>

التكرار 2: الحد الأدنى للطول

<script> // 🔴 الأحمر: أضف اختبارًا جديدًا test('should reject password shorter than 8 characters', () => { const validator = new PasswordValidator(); const result = validator.validate('short'); expect(result.isValid).toBe(false); expect(result.errors).toContain('Password must be at least 8 characters'); }); // ❌ الاختبار يفشل </script>
<script> // 🟢 الأخضر: اجعله ينجح class PasswordValidator { validate(password) { const errors = []; if (!password) { errors.push('Password cannot be empty'); } else if (password.length < 8) { errors.push('Password must be at least 8 characters'); } return { isValid: errors.length === 0, errors }; } } // ✅ جميع الاختبارات تنجح! </script>

التكرار 3: إعادة الهيكلة

<script> // 🔵 إعادة الهيكلة: حسّن التصميم class PasswordValidator { constructor() { this.minLength = 8; } validate(password) { const errors = []; if (!password) { errors.push('Password cannot be empty'); return this.createResult(errors); } if (password.length < this.minLength) { errors.push(`Password must be at least ${this.minLength} characters`); } return this.createResult(errors); } createResult(errors) { return { isValid: errors.length === 0, errors }; } } // ✅ الاختبارات لا تزال تنجح، الكود أكثر قابلية للصيانة! </script>

فوائد TDD

يوفر TDD مزايا عديدة تجعله يستحق منحنى التعلم الأولي:

1. تصميم أفضل

كتابة الاختبارات أولاً يجبرك على التفكير في الواجهات والتصميم قبل التنفيذ. يؤدي هذا إلى كود أكثر نمطية وأقل ارتباطًا.

2. تصحيح أقل

يتم اكتشاف الأخطاء على الفور عند فشل الاختبارات. تعرف بالضبط ما الذي كُسر ومتى.

3. التوثيق

تعمل الاختبارات كوثائق حية توضح كيفية استخدام الكود وما يفعله.

4. الثقة في إعادة الهيكلة

مع الاختبارات الشاملة، يمكنك إعادة الهيكلة دون خوف مع العلم أن الاختبارات ستكتشف أي انحدارات.

5. كود أبسط

يشجع TDD على كتابة الكود المطلوب فقط لتمرير الاختبارات، مما يتجنب الهندسة المفرطة.

6. تطوير أسرع

بينما يبدو TDD أبطأ في البداية، فإنه يسرع التطوير عن طريق تقليل وقت التصحيح ومنع الأخطاء.

نتيجة البحث: تظهر الدراسات أن TDD يمكن أن يقلل كثافة العيوب بنسبة 40-90% مقارنة بالتطوير التقليدي، مع زيادة فقط 15-35% في وقت التطوير.

أفضل ممارسات TDD

  • ابدأ بسيطًا: ابدأ بأبسط حالة اختبار وابنِ التعقيد تدريجيًا
  • اختبار واحد في كل مرة: ركز على جعل اختبار واحد ينجح قبل كتابة التالي
  • خطوات صغيرة: اتخذ خطوات صغيرة—اكتب اختبارات صغيرة وتنفيذات صغيرة
  • اختبر السلوك، وليس التنفيذ: ركز على ما يجب أن يفعله الكود، وليس كيف يفعله
  • احتفظ بالاختبارات سريعة: الاختبارات السريعة تشجع على تشغيلها بشكل متكرر
  • أعد الهيكلة بانتظام: لا تتخطى خطوة إعادة الهيكلة—إنها ضرورية لجودة الكود
  • احذف الكود: إذا كتبت كودًا غير مطلوب لتمرير اختبار، احذفه

أخطاء TDD الشائعة

تجنب هذه الأخطاء:

  • كتابة الاختبارات بعد: كتابة الكود أولاً يهزم غرض TDD
  • اختبار كثير جدًا: لا تختبر تفاصيل التنفيذ أو كود الإطار
  • تخطي إعادة الهيكلة: خطوة إعادة الهيكلة حاسمة لجودة الكود
  • خطوات كبيرة: اتخاذ قفزات كبيرة يجعل من الصعب تحديد المشاكل
  • عدم تشغيل الاختبارات بشكل متكرر: شغل الاختبارات بعد كل تغيير صغير
  • اختبارات هشة: يجب ألا تنكسر الاختبارات عند إعادة هيكلة التنفيذ

متى تستخدم TDD

TDD فعال بشكل خاص في هذه السيناريوهات:

  • منطق الأعمال المعقد: عندما يكون المنطق معقدًا وعرضة للخطأ
  • واجهات برمجة التطبيقات العامة: عندما تحتاج إلى التأكد من الحفاظ على العقود
  • إصلاح الأخطاء: اكتب اختبارًا فاشلًا يعيد إنتاج الخطأ، ثم أصلحه
  • إعادة الهيكلة: توفر الاختبارات شبكة أمان عند إعادة هيكلة الكود
  • تعلم تقنية جديدة: يساعد TDD في فهم المكتبات أو الأطر الجديدة

متى قد لا يناسب TDD

TDD ليس دائمًا أفضل نهج لكل موقف:

  • النماذج الأولية: قد يُعيق TDD التجريب السريع
  • تصميم واجهة المستخدم: من الصعب تطبيق TDD على تكرار التصميم المرئي
  • المتطلبات غير الواضحة: عندما لا تعرف ما الذي تبنيه بعد
  • الكود التافه: الحصول/التعيين البسيط لا يستفيد من TDD

نهج براغماتي: لا يتعين عليك استخدام TDD لـ 100% من الكود الخاص بك. استخدمه حيث يضيف قيمة وتخطاه حيث لا يفعل ذلك.

TDD مقابل الاختبار التقليدي

الجانب الاختبار التقليدي TDD
متى تكتب الاختبارات بعد كتابة الكود قبل كتابة الكود
التركيز التحقق من التنفيذ تحديد السلوك
تصميم الكود التصميم أولاً، الاختبار لاحقًا ينبثق التصميم من الاختبارات
تغطية الاختبار غالبًا غير مكتملة 100% بالتعريف
الثقة قد تفوت الاختبارات الحالات الحدية ثقة عالية في السلوك

مثال على سير عمل TDD

إليك سير عمل TDD كامل لبناء أداة قص النصوص:

<script> // مجموعة الاختبار describe('StringTrimmer', () => { // التكرار 1: القص الأساسي test('should remove leading and trailing spaces', () => { const result = trimString(' hello '); expect(result).toBe('hello'); }); // ✅ نفذ وانجح // التكرار 2: نص فارغ test('should handle empty string', () => { const result = trimString(''); expect(result).toBe(''); }); // ✅ نفذ وانجح // التكرار 3: لا مسافات test('should return same string if no spaces', () => { const result = trimString('hello'); expect(result).toBe('hello'); }); // ✅ ينجح بالفعل! // التكرار 4: مسافات متعددة test('should handle multiple spaces', () => { const result = trimString(' hello world '); expect(result).toBe('hello world'); }); // ✅ نفذ وانجح // التكرار 5: Null/undefined test('should handle null and undefined', () => { expect(trimString(null)).toBe(''); expect(trimString(undefined)).toBe(''); }); // ✅ نفذ وانجح }); </script>

تمرين TDD

مارس TDD من خلال بناء دالة FizzBuzz. اتبع دورة الأحمر-الأخضر-إعادة الهيكلة:

المتطلبات:

  • الأرقام القابلة للقسمة على 3 ترجع "Fizz"
  • الأرقام القابلة للقسمة على 5 ترجع "Buzz"
  • الأرقام القابلة للقسمة على كليهما ترجع "FizzBuzz"
  • الأرقام الأخرى ترجع الرقم كنص

الخطوات:

  1. اكتب اختبارًا للأرقام القابلة للقسمة على 3
  2. اكتب الحد الأدنى من الكود للنجاح
  3. اكتب اختبارًا للأرقام القابلة للقسمة على 5
  4. حدّث الكود لتمرير كلا الاختبارين
  5. اكتب اختبارًا للأرقام القابلة للقسمة على كليهما
  6. حدّث الكود لتمرير جميع الاختبارات
  7. اكتب اختبارًا للأرقام الأخرى
  8. أكمل التنفيذ
  9. أعد الهيكلة إذا لزم الأمر

الخلاصة

التطوير الموجه بالاختبار هو منهجية قوية تحسن جودة الكود والتصميم والثقة. من خلال اتباع دورة الأحمر-الأخضر-إعادة الهيكلة، تكتب اختبارات أفضل وكودًا أنظف وتكتشف الأخطاء مبكرًا. بينما يحتوي TDD على منحنى تعلم، فإن الفوائد في جودة الكود وقابلية الصيانة تجعله يستحق العناء لمعظم سيناريوهات التطوير.

النقاط الرئيسية:

  • TDD يتبع دورة الأحمر-الأخضر-إعادة الهيكلة
  • اكتب اختبارات فاشلة قبل كتابة الكود
  • اكتب الحد الأدنى من الكود لجعل الاختبارات تنجح
  • أعد الهيكلة لتحسين الجودة مع الحفاظ على نجاح الاختبارات
  • TDD يؤدي إلى تصميم أفضل وأخطاء أقل
  • استخدم TDD حيث يضيف قيمة، وليس في كل مكان بشكل أعمى