أهلاً بك في هذا الدرس!
بعد أن تعرفنا في الدرس السابق على ماهية اختبارات الوحدة، قد يتبادر إلى ذهنك سؤال مهم: هل اختبارات الوحدة هي النوع الوحيد من الاختبارات الذي أحتاجه؟ الجواب المختصر هو "لا".
عالم تطوير البرمجيات يشبه ورشة عمل ضخمة لبناء آلات معقدة. تخيل أنك تبني سيارة. هل تكتفي باختبار كل بُرغي وصمولة على حدة وتفترض أن السيارة ستعمل بكفاءة؟ بالطبع لا! ستحتاج إلى اختبار المحرك بعد تجميعه، ثم اختبار نظام التوجيه مع العجلات، وأخيراً، ستقوم بقيادة السيارة بأكملها في الشارع للتأكد من أنها آمنة وجاهزة للاستخدام.
بنفس المنطق، تحتوي تطبيقاتنا على مستويات مختلفة من التعقيد، وكل مستوى يحتاج إلى نوع معين من الاختبارات للتأكد من جودته. في هذا الدرس، سنستكشف الأنواع الرئيسية للاختبارات البرمجية وكيف تتكامل معًا لبناء تطبيقات قوية وموثوقة.

هرم الاختبارات (The Testing Pyramid)
قبل أن نتعمق في تفاصيل كل نوع، من المهم أن نفهم مفهومًا أساسيًا يسمى "هرم الاختبارات" (The Testing Pyramid). هذا النموذج البصري يساعدنا على توزيع جهودنا في الاختبار بشكل فعال.
تخيل هرمًا مقسمًا إلى ثلاث طبقات:
- القاعدة (الأعرض): اختبارات الوحدة (Unit Tests): هي الأكثر عددًا. إنها سريعة في التنفيذ، سهلة الكتابة، وتركز على أصغر أجزاء الكود. لأنها تشكل قاعدة الهرم، فهي أساس استراتيجية الاختبار لدينا.
- الوسط: اختبارات التكامل (Integration Tests): أقل عددًا من اختبارات الوحدة. تختبر كيفية تفاعل عدة وحدات معًا. هي أبطأ قليلاً وأكثر تعقيدًا في الإعداد.
- القمة (الأضيق): اختبارات الطرف-إلى-الطرف (End-to-End Tests): هي الأقل عددًا على الإطلاق. تحاكي رحلة المستخدم الكاملة عبر التطبيق. هي الأبطأ، والأكثر تكلفة في الصيانة، ولكنها تمنحنا أعلى درجات الثقة في أن النظام يعمل كما هو متوقع من وجهة نظر المستخدم.
الآن، دعنا نفصّل كل طبقة من هذه الطبقات.
1. اختبارات الوحدة (Unit Tests)
هذه هي الاختبارات التي سنركز عليها بشكل أساسي في هذه الدورة.
ما هي؟
اختبار الوحدة هو اختبار لأصغر جزء منطقي معزول في تطبيقك. هذا الجزء يمكن أن يكون دالة (function)، أو Method في Class، أو مكون واجهة مستخدم (UI Component) في أبسط حالاته.
الكلمة المفتاحية هنا هي "معزول". عند كتابة اختبار وحدة، نحاول عزل الوحدة قيد الاختبار عن بقية أجزاء النظام (مثل الشبكة، قواعد البيانات، أو الوحدات الأخرى). هذا يجعل الاختبارات سريعة جدًا ومستقرة، فعندما يفشل اختبار وحدة، فأنت تعرف بالضبط أين تكمن المشكلة.
مثال عملي
لنفترض أن لديك دالة بسيطة تحسب السعر الإجمالي للمنتج بعد إضافة الضريبة.
// file: priceUtils.js
export function calculateTotalPrice(basePrice, taxRate) {
if (basePrice < 0 || taxRate < 0 || taxRate > 1) {
throw new Error('Invalid input values');
}
const taxAmount = basePrice * taxRate;
return basePrice + taxAmount;
}اختبار الوحدة لهذه الدالة سيبدو كالتالي:
// file: priceUtils.test.js
import { calculateTotalPrice } from './priceUtils';
test('should return the correct total price with tax', () => {
// Arrange: Set up our variables
const price = 100;
const tax = 0.15; // 15%
// Act: Call the function we are testing
const totalPrice = calculateTotalPrice(price, tax);
// Assert: Check if the result is what we expect
expect(totalPrice).toBe(115);
});
test('should throw an error for negative base price', () => {
// We expect the function to throw an error in this case
expect(() => calculateTotalPrice(-50, 0.15)).toThrow('Invalid input values');
});لاحظ كيف أننا نختبر فقط منطق دالة calculateTotalPrice دون أي اعتماديات خارجية.
2. اختبارات التكامل (Integration Tests)
هذه هي الطبقة التالية في الهرم. هنا، نبدأ في تجميع الوحدات التي اختبرناها بشكل فردي لنرى كيف تعمل معًا.
ما هي؟
تتحقق اختبارات التكامل من أن وحدتين أو أكثر من وحدات الكود تعملان معًا بشكل صحيح. على سبيل المثال، هل يستطيع مكون واجهة المستخدم (UI Component) جلب البيانات بنجاح من خدمة API وعرضها؟ هل يقوم الـ Controller بحفظ البيانات بشكل صحيح في قاعدة البيانات عبر الـ Service Layer؟
مثال عملي
تخيل أن لديك وحدة لجلب بيانات المستخدمين (userService) ومكون واجهة مستخدم (UserAvatar) يعرض صورة المستخدم واسمه.
// file: userService.js
// This service fetches user data from an API
export const userService = {
async getUser(userId) {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
return null;
}
return response.json();
}
};
// file: UserAvatar.js (Conceptual)
// A UI component that uses the userService
import { userService } from './userService';
export async function displayUserAvatar(userId) {
const user = await userService.getUser(userId);
if (user) {
// Logic to update the DOM with user.name and user.avatarUrl
document.getElementById('avatar').src = user.avatarUrl;
document.getElementById('username').textContent = user.name;
} else {
// Display a default avatar
}
}اختبار التكامل هنا لن يختبر userService أو UserAvatar بشكل معزول، بل سيختبرهما معًا. قد يتضمن الاختبار استدعاء displayUserAvatar والتأكد من أنها تستدعي userService.getUser ثم تقوم بتحديث الـ DOM بالبيانات المستلمة.
3. اختبارات الطرف-إلى-الطرف (End-to-End Tests)
هذه هي قمة الهرم، وهي تحاكي تجربة المستخدم الحقيقية بأكملها.
ما هي؟
اختبارات الطرف-إلى-الطرف (E2E) تقوم بأتمتة متصفح حقيقي (أو تطبيق موبايل) لتنفيذ سيناريو مستخدم كامل من البداية إلى النهاية. الهدف هو التحقق من أن تدفقات العمل (Workflows) الرئيسية في تطبيقك تعمل كما هو متوقع في بيئة تشبه البيئة الحقيقية قدر الإمكان.
مثال عملي
لنفترض أن لديك متجرًا إلكترونيًا. سيناريو E2E نموذجي قد يكون "إضافة منتج إلى عربة التسوق وإتمام عملية الشراء".
سيبدو الاختبار (مكتوبًا بلغة وصفية) كالتالي:
- افتح المتصفح وانتقل إلى الصفحة الرئيسية للموقع.
- ابحث عن منتج يسمى "سماعات رأس لاسلكية".
- انقر على المنتج للدخول إلى صفحته.
- انقر على زر "إضافة إلى العربة".
- انتقل إلى صفحة عربة التسوق.
- تحقق من أن المنتج والسعر الصحيحين موجودان في العربة.
- انقر على زر "الانتقال إلى الدفع".
- املأ معلومات الشحن والدفع.
- انقر على زر "تأكيد الطلب".
- تحقق من أنك ترى صفحة "شكرًا لك" ورقم الطلب.
تُستخدم أدوات مثل Cypress أو Playwright لكتابة هذا النوع من الاختبارات.
مقارنة سريعة: متى تختار أي نوع؟
لمساعدتك على تذكر الفروقات، إليك جدول مقارنة بسيط:
| الخاصية | اختبارات الوحدة (Unit) | اختبارات التكامل (Integration) | اختبارات الطرف-إلى-الطرف (E2E) |
|---|---|---|---|
| النطاق (Scope) | دالة أو مكون واحد | تفاعل عدة وحدات معًا | رحلة مستخدم كاملة عبر التطبيق |
| السرعة (Speed) | سريعة جدًا (مللي ثانية) | متوسطة (ثوانٍ) | بطيئة جدًا (دقائق أحيانًا) |
| التكلفة (Cost) | منخفضة للكتابة والصيانة | متوسطة | عالية جدًا للكتابة والصيانة |
| الثقة (Confidence) | منخفضة (تضمن صحة جزء صغير) | متوسطة (تضمن تفاعل الأجزاء) | عالية جدًا (تضمن عمل النظام ككل) |
| الكمية (Quantity) | كثيرة جدًا (المئات أو الآلاف) | معتدلة | قليلة (عشرات) |
ما هو تركيز هذه الدورة؟
كما يوحي اسم الدورة، سيكون تركيزنا الأساسي على اختبارات الوحدة (Unit Tests). لماذا؟ لأنها تشكل الأساس لأي استراتيجية اختبار ناجحة. إنها تجبرنا على كتابة كود أفضل، أكثر تنظيمًا وقابلية للصيانة. إتقان اختبارات الوحدة هو الخطوة الأولى والأكثر أهمية لأي مطور يسعى لتحسين جودة عمله.
مع ذلك، كان من الضروري أن تفهم الصورة الكاملة لتعرف أين تقع اختبارات الوحدة في عالم الاختبارات البرمجية، وكيف تكملها الأنواع الأخرى.
خلاصة
في هذا الدرس، تعلمنا أن هناك أنواعًا مختلفة من الاختبارات، ولكل منها غرضه الخاص:
- اختبارات الوحدة (Unit Tests): تختبر أصغر أجزاء الكود بشكل معزول. إنها سريعة وتشكل أساس هرم الاختبارات.
- اختبارات التكامل (Integration Tests): تتحقق من أن الوحدات المختلفة تعمل معًا كما هو متوقع.
- اختبارات الطرف-إلى-الطرف (E2E Tests): تحاكي سلوك المستخدم الحقيقي وتختبر تدفقات العمل الكاملة في التطبيق.
استراتيجية الاختبار القوية لا تعتمد على نوع واحد فقط، بل تستخدم مزيجًا متوازنًا من هذه الأنواع الثلاثة، مع التركيز بشكل أكبر على اختبارات الوحدة. الآن بعد أن أصبحت لديك هذه الخلفية، أنت جاهز للانتقال إلى الخطوة التالية: اختيار وإعداد أدواتنا لبدء كتابة أول اختبار وحدة!

إرسال تعليق