إعادة بناء بثقة عبر الاختبارات
Refactor with Tests
إعادة بناء بثقة عبر الاختبارات — Refactor with Tests
الاختبار ليس نشاطاً يحدث بعد الكتابة. هو أداة تُشكّل الكود منذ البداية وتمنحك الجرأة على تغييره لاحقاً دون خوف.
في هذا الدرس سنمر بدورة كاملة:
- نستلم دالة تعمل لكنها مكتنزة بالشروط المتداخلة والأرقام السحرية
- نكتب الاختبارات قبل أي تعديل — نثبّت السلوك الحالي
- نُعيد البناء خطوة بخطوة مع تشغيل الاختبارات بعد كل خطوة
- نُضيف ميزة جديدة باستخدام TDD — الاختبار أولاً ثم التنفيذ
الحالة: حاسبة الخصومات
لدينا دالة تحسب خصم طلب في متجر إلكتروني. هي تعمل، لكنها صعبة القراءة وأصعب الصيانة:
def calculate_discount(order_total, customer_type, coupon_code):
if customer_type == "vip":
if order_total >= 1000:
discount = order_total * 0.30
elif order_total >= 500:
discount = order_total * 0.20
else:
discount = order_total * 0.10
elif customer_type == "regular":
if order_total >= 500:
discount = order_total * 0.10
else:
discount = 0
else:
discount = 0
if coupon_code == "SAVE50":
discount += 50
elif coupon_code == "PERCENT5":
discount += order_total * 0.05
return min(discount, order_total)
المشكلات واضحة: الأرقام السحرية 0.30, 0.20, 0.10, 500, 1000 موزّعة في الكود، الشروط متداخلة ثلاثة مستويات، وأي تغيير في سياسة الخصومات يستدعي البحث في كل فرع.
الخطوة الأولى: ثبّت السلوك بالاختبارات
قبل لمس أي سطر، نكتب اختبارات تُغطي كل الحالات الحدّية. هذا العقد الذي يجب أن يظل صحيحاً بعد التعديل.
الاختبارات خضراء. الآن نعرف بالضبط ما يجب أن يظل صحيحاً. نبدأ التعديل.
الخطوة الثانية: استخرج الثوابت
الأرقام السحرية تجعل الكود صعب القراءة وخطير الصيانة. نُعطيها أسماء واضحة:
الاختبارات لا تزال خضراء. استخراج الثوابت لم يُغيّر أي سلوك.
الخطوة الثالثة: استخرج دوال متخصصة
نُفصل منطق خصم النوع عن منطق الكوبون — كل دالة مسؤولية واحدة:
الكود الآن:
_vip_discountمسؤولة فقط عن منطق VIP_base_discountتفوّض لـ_vip_discountأو تُحسب مباشرة_coupon_discountمعزولة تماماً عن منطق النوعcalculate_discountتُجمّع النتائج فقط
أي تغيير في سياسة VIP يُعدَّل في مكان واحد فقط. الاختبارات تُثبت أن الكل يعمل.
الخطوة الرابعة: TDD — اختبار أولاً
جاء طلب جديد: أضف كوبون WELCOME10 يُعطي 10% لكل الطلبات بدون حد أدنى.
في TDD نكتب الاختبار أولاً، ونتأكد أنه يفشل (لأن الكود لم يُطبّق بعد)، ثم نُنفّذ الكود حتى ينجح.
الخطوات:
- اكتب الاختبار → أحمر (فشل)
- اكتب أبسط كود يُنجحه → أخضر
- نظّف الكود إن لزم → إعادة بناء
كل الاختبارات — القديمة والجديدة — خضراء. هذا ما يعنيه TDD: الاختبار يُحدد العقد، الكود يُوفّيه.
مبادئ إعادة البناء الآمن
قبل أي تعديل:
- اكتب اختبارات تُغطي كل الحالات الحدية (العوارض: حد التشعّب، القيم الصفرية، الطرف السفلي والعلوي)
- تأكد أن الاختبارات تفشل إذا عدّلت نتيجة متوقعة — الاختبار الذي لا يستطيع الفشل لا يحميك
أثناء التعديل:
- خطوة صغيرة واحدة في كل مرة — استخرج ثابتاً، ثم دالة، ثم اختبرها
- شغّل الاختبارات بعد كل تغيير، لا بعد مجموعة تغييرات
- إذا احمرّ اختبار، تراجع فوراً وافهم السبب قبل المتابعة
الفرق بين إعادة البناء وتغيير الميزة:
- إعادة البناء: نفس السلوك، شكل أفضل — الاختبارات القديمة لا تتغير
- تغيير الميزة: سلوك جديد — تحتاج اختبارات جديدة
لا تخلطهما في commit واحد.
التحدي الأول: اكتب الاختبار الفاشل أولاً
التحدي الثاني: أضف ميزة بـ TDD
خلاصة الدرس
الاختبارات ليست قيداً — هي شبكة أمان تُحرّرك من الخوف. بدونها، كل تعديل محفوف بمخاطر كسر شيء لم تُلاحظه. معها، يمكنك إعادة بناء الكود بثقة لأنك تعرف على الفور إذا كسرت شيئاً.
دورة العمل المثالية:
- اكتب اختبارات تُغطي السلوك الحالي
- عدّل خطوة صغيرة — ثابت، دالة، تبسيط شرط
- شغّل الاختبارات — كلها خضراء؟ تابع. وجد أحمر؟ تراجع وافهم
- أضف ميزة بـ TDD — اختبار أحمر أولاً، ثم اجعله أخضر، ثم نظّف
هذا ما تفعله الفرق الاحترافية يومياً.