اختبار الوحدة (Unit Testing) هو حجر الأساس في اختبار البرمجيات، وأكثر أنواع الاختبارات التي ستكتبها في حياتك المهنية. هذه الصفحة هي نقطة البداية لمسار الاختبار؛ مفاهيمها مستقلّة عن أي لغة أو إطار عمل، وتنطبق على Python وJavaScript وJava وغيرها بالقدر نفسه.
ما هو اختبار الوحدة؟
اختبار الوحدة هو كود تكتبه ليتحقّق آليًا من أن جزءًا صغيرًا ومعزولًا من برنامجك يعمل كما هو متوقّع.
بدل أن تشغّل التطبيق كلّه وتتحقّق يدويًا في كل مرة، تكتب اختبارًا صغيرًا يستدعي دالّة معيّنة بمدخلات محدّدة، ويتأكّد أن مخرجاتها هي المتوقّعة. ثم يمكنك تشغيل آلاف هذه الاختبارات في ثوانٍ عند كل تغيير.
اختبار الوحدة يجيب عن سؤال واحد: «هل ما زالت هذه القطعة الصغيرة من الكود تفعل ما يُفترض بها؟»
ما «الوحدة» التي نختبرها؟
الوحدة (Unit) هي أصغر قطعة قابلة للاختبار بمعزل عن غيرها — عادةً دالّة (function) أو ميثود (method) واحدة. المعيار العملي: الوحدة تؤدّي مسؤولية واحدة واضحة يمكن التحقّق منها وحدها.
القاعدة المهمّة: العزل. اختبار الوحدة يفحص منطق الوحدة نفسها فقط، لا قاعدة البيانات ولا الشبكة ولا نظام الملفات. إن اعتمدت الوحدة على هذه الأشياء، نستبدلها ببدائل اختبارية (سنشرحها بعد قليل).
لماذا يهم اختبار الوحدة؟
- يكشف الأعطال مبكرًا: تجد الخطأ لحظة كتابته، لا بعد وصوله للمستخدم.
- يمنحك الثقة في التعديل (Refactoring): تغيّر الكود وأنت مطمئن؛ إن كسرت شيئًا أخبرك الاختبار فورًا.
- يوثّق السلوك المتوقّع: اختبار جيّد يقرأ كمواصفات حيّة تشرح ماذا يُفترض أن تفعل الوحدة.
- سريع ورخيص: يعمل في أجزاء من الثانية، فيمكن تشغيله عند كل حفظ أو في خطّ التكامل المستمر (CI).
بنية اختبار الوحدة: Arrange-Act-Assert
معظم اختبارات الوحدة تتبع بنية من ثلاث خطوات تُعرف بـ AAA:
- Arrange (تهيئة): جهّز المدخلات والحالة اللازمة.
- Act (تنفيذ): استدعِ الوحدة قيد الاختبار.
- Assert (توكيد): تحقّق أن النتيجة مطابقة للمتوقّع.
مثال بلغة JavaScript دون أي إطار اختبار — لتبقى الفكرة واضحة (الوحدة دالّة نقيّة تحسب السعر بعد خصم):
// الوحدة قيد الاختبار
function applyDiscount(price, percent) {
if (percent < 0 || percent > 100) throw new Error("نسبة غير صالحة");
return price - (price * percent) / 100;
}
// اختبار وحدة يتبع بنية Arrange-Act-Assert
function test_applyDiscount_يخصم_النسبة_الصحيحة() {
// Arrange
const price = 200;
const percent = 10;
// Act
const result = applyDiscount(price, percent);
// Assert
if (result !== 180) {
throw new Error(`توقّعت 180 لكن حصلت على ${result}`);
}
}
test_applyDiscount_يخصم_النسبة_الصحيحة(); // ينجح بصمت؛ يرمي خطأً لو فشل
في المشاريع الحقيقية تستخدم إطار اختبار (مثل Jest أو PyTest) يوفّر توكيدات جاهزة وتقارير أوضح، لكن الجوهر يبقى هو بنية AAA نفسها.
العزل والتوكيدات
- التوكيد (Assertion): العبارة التي تقارن النتيجة الفعلية بالمتوقّعة وتُفشل الاختبار عند الاختلاف. اختبار بلا توكيد ليس اختبارًا.
- العزل (Isolation): كل اختبار يجب أن يعمل مستقلًا، دون الاعتماد على ترتيب تشغيل الاختبارات أو على حالة خارجية مشتركة. اختبار الوحدة الجيّد حتمي (Deterministic): يعطي النتيجة نفسها في كل مرة.
البدائل الاختبارية (Test Doubles)
حين تعتمد الوحدة على مكوّن خارجي (قاعدة بيانات، خدمة شبكة، الوقت الحالي)، نستبدل ذلك المكوّن ببديل اختباري لنبقي الاختبار سريعًا ومعزولًا وحتميًا. أشهر الأنواع:
- Stub: يعيد قيمة ثابتة جاهزة (مثلًا: «افترض أنّ الاستعلام أعاد هذا المستخدم»).
- Mock: بديل نتحقّق أنه استُدعي بالشكل الصحيح (مثلًا: أنّ دالّة إرسال البريد نُودِيت مرّة واحدة بالوسائط الصحيحة).
- Fake: نسخة مبسّطة عاملة (مثلًا: قاعدة بيانات في الذاكرة بدل قاعدة حقيقية).
⚠️ لا تُفرط في المحاكاة (Over-mocking). كثرة البدائل تجعل الاختبار يفحص «كيف نُفّذ الكود» بدل «ماذا ينتج»، فيصبح هشًّا ويكسر عند أي إعادة هيكلة بريئة.
اختبار وحدة جيّد مقابل سيّئ
| اختبار جيّد | اختبار سيّئ |
|---|---|
| سريع (أجزاء من الثانية) | بطيء (يلمس قاعدة بيانات/شبكة) |
| حتمي (نتيجة ثابتة) | متقلّب (Flaky) ينجح ويفشل عشوائيًا |
| يفحص سلوكًا واحدًا واضحًا | يفحص أشياء كثيرة دفعة واحدة |
| اسمه يصف ما يتحقّق منه | اسم غامض مثل test1 |
| يفحص المخرجات (السلوك) | يفحص تفاصيل التنفيذ الداخلية |
أخطاء شائعة
- اختبار تفاصيل التنفيذ بدل السلوك الظاهر؛ يجعل الاختبار يكسر مع كل إعادة هيكلة.
- اختبارات متقلّبة (Flaky) تعتمد على الوقت أو الترتيب أو موارد خارجية.
- مطاردة نسبة التغطية (Coverage) كهدف بحدّ ذاته؛ تغطية 100% لا تعني أن الاختبارات ذات معنى.
- توكيد واحد ضخم غامض بدل توكيدات واضحة تقول بالضبط ما الذي فشل.
علاقته باختبار التكامل و E2E
اختبار الوحدة طبقة واحدة ضمن هرم الاختبار (Testing Pyramid):
- اختبار الوحدة: كثير وسريع؛ يفحص أجزاءً معزولة. (قاعدة الهرم)
- اختبار التكامل (Integration): أقل عددًا؛ يفحص تعاون عدّة وحدات معًا (مثلًا: دالّة + قاعدة بيانات حقيقية).
- الاختبار الطرفي (End-to-End): الأقل؛ يفحص التطبيق كاملًا من منظور المستخدم، وهو الأبطأ والأغلى.
القاعدة العملية: اجعل معظم اختباراتك وحداتٍ سريعة، واحتفظ بعدد أقلّ من اختبارات التكامل و E2E للمسارات الحرجة.
الخطوات التالية
بعد إتقان هذه الأساسيات، ستنتقل إلى: هرم الاختبار بتفصيل أوسع، ثم التطوير الموجَّه بالاختبار (TDD)، ثم كتابة أول اختبار عملي واختبار التكامل و E2E.
ولأن اختبار الوحدة يُطبَّق داخل كل لغة وإطار، يمكنك رؤيته في سياق عملي في مساراتنا الحالية: الاختبار في React، والاختبار في Node.js، وأنماط اختبار التصاميم البرمجية.