أهلاً بك في الجزء العملي والممتع! بعد أن قمنا في الدرس السابق بتهيئة بيئة الاختبار باستخدام Vitest، حان الوقت الآن لنتعلم كيف نكتب أول اختبار وحدة (Unit Test) لنا. قد تبدو الخطوة الأولى بسيطة، لكنها في الحقيقة حجر الأساس الذي ستبني عليه كل مهاراتك في الاختبارات لاحقًا.
في هذا الدرس، سنغوص في المفاهيم الأساسية التي تشكل هيكل أي اختبار، وهي:
describe: لتنظيم وتجميع الاختبارات ذات الصلة.it(أوtest): لتعريف حالة اختبار فردية.expect: للتأكيد والتحقق من صحة النتائج.
بنهاية هذا الدرس، ستكون قد كتبت بنفسك اختبارًا متكاملًا، وقمت بتشغيله، وفهمت كيفية قراءة نتائجه سواء نجح أم فشل.

تشريح ملف الاختبار (Anatomy of a Test File)
قبل أن نكتب أي كود، من المهم أن نفهم كيف يتعرف Vitest على ملفات الاختبارات الخاصة بنا. القاعدة بسيطة: يتبع Vitest نمطًا معينًا في أسماء الملفات. بشكل افتراضي، سيبحث عن أي ملف ينتهي بـ:
.test.js.spec.js.test.ts.spec.ts
كلمة spec هي اختصار لـ Specification (مواصفات)، وهي مجرد اصطلاح آخر شائع في عالم الاختبارات. يمكنك استخدام أي منهما، ولكننا سنعتمد .test.js في هذه الدورة للوضوح.
لنفترض أن لدينا ملفًا بسيطًا يحتوي على دالة نريد اختبارها. لنقم بإنشاء ملف باسم math.js:
// src/math.js
export function sum(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new Error('Both arguments must be numbers.');
}
return a + b;
}الآن، لنكتب اختبارًا لهذه الدالة. سنقوم بإنشاء ملف جديد بجانب الملف الأصلي (أو في مجلد __tests__) ونسميه math.test.js.
// src/math.test.js
// We will write our test code hereوجود هذا الملف بالاسم الصحيح هو كل ما يحتاجه Vitest لاكتشافه وتشغيله تلقائيًا.
مجموعة الاختبارات (Test Suite): استخدام describe
عندما تبدأ في كتابة الاختبارات، ستجد أن لديك العديد من الاختبارات المتعلقة بجزء معين من الكود (وحدة أو module). لتنظيم هذه الاختبارات وجعلها أكثر قابلية للقراءة، نستخدم الدالة describe.
describe تقوم بإنشاء "مجموعة اختبارات" (Test Suite). فكر فيها كأنها مجلد ينظم ملفاتك، أو فصل في كتاب يجمع الفقرات المتعلقة بنفس الموضوع.
هيكلها كالتالي:
// describe(name, fn)
describe('sum function', () => {
// All tests related to the 'sum' function will go here
});- الوسيط الأول
name: هو سلسلة نصية تصف المجموعة. يجب أن يكون الوصف واضحًا، مثل اسم الدالة أو الوحدة التي تختبرها. - الوسيط الثاني
fn: هي دالة (callback function) ستحتوي على جميع الاختبارات (itblocks) الخاصة بهذه المجموعة.
يمكنك أيضًا وضع describe داخل describe أخرى لإنشاء تنظيم هرمي أكثر دقة.
حالة الاختبار (Test Case): استخدام it أو test
داخل مجموعة describe، نحدد كل حالة اختبار فردية باستخدام الدالة it (أو test، فهما اسمان لنفس الدالة).
يمثل كل it block سيناريو واحدًا محددًا نريد اختباره. يجب أن يكون اسم الاختبار وصفيًا جدًا بحيث يقرأ كجملة إنجليزية مفهومة.
describe('sum function', () => {
it('should return the correct sum of two positive numbers', () => {
// Test logic goes here
});
it('should throw an error if arguments are not numbers', () => {
// Another specific test case
});
});لاحظ كيف يقرأ الاسم: "The sum function... it should return the correct sum...". هذا الأسلوب يجعل الغرض من الاختبار واضحًا تمامًا حتى بدون قراءة الكود بداخله.
التأكيد (Assertion): استخدام expect
هنا يكمن جوهر الاختبار. كيف نتحقق فعليًا من أن الكود يعمل كما هو متوقع؟ باستخدام expect.
دالة expect تتيح لنا إنشاء "تأكيدات" (Assertions) حول قيمة معينة. نمرر لها القيمة التي حصلنا عليها من دالتنا (الناتج الفعلي)، ثم نستخدم دوالًا ملحقة بها تسمى "Matchers" لنقارن هذا الناتج بالقيمة المتوقعة.
أبسط وأشهر Matcher هو .toBe()، الذي يتحقق من المساواة الصارمة (strict equality) باستخدام ===.
دعنا نرى كيف نستخدمه داخل حالة الاختبار الخاصة بنا. تتبع الاختبارات عادةً نمطًا من ثلاث خطوات يُعرف باسم Arrange-Act-Assert (AAA):
- Arrange (الإعداد): جهّز كل المتغيرات والمدخلات التي يحتاجها اختبارك.
- Act (التنفيذ): استدعِ الدالة أو الوظيفة التي تريد اختبارها.
- Assert (التأكيد): استخدم
expectللتحقق من أن الناتج صحيح.
it('should return the correct sum of two positive numbers', () => {
// 1. Arrange
const num1 = 5;
const num2 = 10;
const expectedResult = 15;
// 2. Act
const actualResult = sum(num1, num2);
// 3. Assert
expect(actualResult).toBe(expectedResult);
});هذا النمط يجعل اختباراتك واضحة ومنظمة وسهلة الفهم.
لنجمع كل شيء معًا: أول اختبار متكامل لك
الآن بعد أن فهمنا كل الأجزاء، لنكتب ملف math.test.js كاملاً.
// src/math.test.js
// Import the functions we need from vitest
import { describe, it, expect } from 'vitest';
// Import the function we want to test
import { sum } from './math';
describe('sum function', () => {
it('should return the correct sum of two positive numbers', () => {
// Arrange
const a = 2;
const b = 3;
// Act
const result = sum(a, b);
// Assert
expect(result).toBe(5);
});
it('should return a correct sum with a negative number', () => {
// Arrange
const a = 10;
const b = -2;
// Act
const result = sum(a, b);
// Assert
expect(result).toBe(8);
});
it('should return zero when summing zero with zero', () => {
// Arrange & Act
const result = sum(0, 0);
// Assert
expect(result).toBe(0);
});
});الآن، اذهب إلى الطرفية (terminal) وقم بتشغيل الأمر الذي أعددناه في الدرس السابق:
npm testإذا كان كل شيء صحيحًا، سترى مخرجات شبيهة بهذه، تخبرك أن جميع الاختبارات قد نجحت:
✓ src/math.test.js (3)
✓ sum function (3)
✓ should return the correct sum of two positive numbers
✓ should return a correct sum with a negative number
✓ should return zero when summing zero with zero
Test Files 1 passed (1)
Tests 3 passed (3)
Start at 10:00:00
Duration 150ms (transform 50ms, setup 0ms, collect 10ms, tests 5ms)تهانينا! لقد كتبت وشغّلت بنجاح أول اختبارات وحدة لك!
ماذا يحدث عندما يفشل الاختبار؟
الاختبارات الناجحة رائعة، لكن القيمة الحقيقية للاختبارات تظهر عندما تفشل، لأنها تنبهنا إلى وجود خطأ. لنجرب هذا عمدًا.
غيّر أحد التأكيدات في ملف math.test.js ليكون خاطئًا:
// In the first test case
expect(result).toBe(999); // This is intentionally wrongالآن شغّل npm test مرة أخرى. سترى مخرجات مختلفة تمامًا وملونة بالأحمر، تخبرك بالضبط بما حدث:
❯ src/math.test.js (3) > sum function (3) > should return the correct sum of two positive numbers
AssertionError: expected 5 to be 999 // [!code error]
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Expected: 999
Received: 5
at /path/to/your/project/src/math.test.js:17:22هذا التقرير هو أفضل صديق لك. لاحظ كيف يخبرك:
- بالمجموعة (
describe) وحالة الاختبار (it) التي فشلت. - بالسبب الدقيق للفشل:
expected 5 to be 999(كان متوقعًا 999 ولكنه استلم 5). - بالملف ورقم السطر الذي حدث فيه الخطأ.
خلاصة
لقد قطعت شوطًا كبيرًا في هذا الدرس! دعنا نلخص ما تعلمته:
- تُكتشف ملفات الاختبار تلقائيًا بناءً على نمط اسمها (مثل
*.test.js). - نستخدم
describeلتنظيم الاختبارات في مجموعات منطقية (Test Suites). - نستخدم
it(أوtest) لتعريف كل حالة اختبار فردية (Test Case) بوصف واضح. - نستخدم
expectمع دوالMatcherمثل.toBe()لعمل تأكيدات (Assertions) والتحقق من صحة الكود. - نمط Arrange-Act-Assert هو طريقة ممتازة لهيكلة اختباراتك لجعلها قابلة للقراءة والصيانة.
- تقارير الفشل في
Vitestمفصلة وتساعدك على تحديد مكان المشكلة بسرعة.
أنت الآن جاهز للانتقال إلى المستوى التالي وتطبيق هذه المفاهيم بنفسك. في الدرس التالي، ستقوم بحل تمرين عملي لترسيخ هذه المهارات الجديدة. استعد لاختبار دالة fizzBuzz الشهيرة!

إرسال تعليق