كتابة تأكيدات (Assertions) جيدة

الكاتب: قالب اقرأتاريخ النشر: آخر تحديث: وقت القراءة:
للقراءة
عدد الكلمات:
كلمة
عدد التعليقات: 0 تعليق
نبذة عن المقال: تعلم كيفية كتابة تأكيدات (assertions) واضحة وفعالة في اختبارات الوحدات. اكتشف أفضل الممارسات لتسهيل تصحيح الأخطاء وتحسين جودة اختباراتك.

في الدرس السابق، تعرفنا على مجموعة متنوعة من الـ Matchers التي توفرها أطر عمل الاختبارات مثل Vitest و Jest. لكن استخدام الـ Matcher الصحيح هو نصف المعركة فقط. النصف الآخر، والأكثر أهمية، هو كيفية صياغة تأكيداتك (Assertions) بطريقة تجعل اختباراتك قوية وموثوقة وسهلة الفهم عند فشلها.

التأكيد (Assertion) ليس مجرد سطر للتحقق من صحة الكود. إنه بمثابة عقد بين اختبارك والكود الذي تختبره. والأهم من ذلك، إنه أول دليل ستبحث عنه عندما يفشل الاختبار. تأكيد ضعيف سيتركك في حيرة، بينما تأكيد جيد سيوجهك مباشرةً إلى مصدر المشكلة.

كتابة تأكيدات (Assertions) جيدة

خصائص التأكيد الجيد (Good Assertion)

دعنا نفكر في التأكيد الجيد كمحقق بارع. لا يكتفي بالقول "هناك خطأ ما"، بل يحدد بدقة "ما هو الخطأ، وأين، ولماذا هو خطأ". لنرَ كيف نحقق ذلك.

1. الوضوح والدقة (Clarity and Precision)

يجب أن يكون التأكيد سهل القراءة والفهم، وأن يختبر شيئًا واحدًا ومحددًا للغاية.

مثال سيء (غامض):

لنفترض أن لدينا دالة تنشئ كائن مستخدم جديد. قد يبدو الاختبار الأولي هكذا:

JavaScript
// src/user.js
export function createUser(name, email) {
  if (!name || !email) return null;
  return {
    id: Date.now(),
    name,
    email,
    createdAt: new Date()
  };
}

// tests/user.test.js
import { createUser } from '../src/user';

it('should create a user object', () => {
  const user = createUser('John Doe', 'john.doe@example.com');
  // This assertion is too vague. What does 'truthy' even mean here?
  // It could be an object, a number, a string... 
  expect(user).toBeTruthy(); 
});

هذا التأكيد يخبرنا فقط أن الدالة لم تُرجع قيمة falsy (مثل null أو undefined). إذا فشل هذا الاختبار، لن نعرف السبب الحقيقي. هل أرجعت الدالة قيمة غير متوقعة؟ هل الكائن فارغ؟

مثال جيد (دقيق):

يمكننا تحسين هذا الاختبار بشكل كبير بجعله أكثر تحديدًا.

JavaScript
// tests/user.test.js
import { createUser } from '../src/user';

it('should create a user object with the correct name and email', () => {
  const name = 'John Doe';
  const email = 'john.doe@example.com';
  const user = createUser(name, email);

  // More specific assertions
  expect(user).not.toBeNull();
  expect(user.name).toBe(name);
  expect(user.email).toBe(email);
});

الآن، إذا فشل الاختبار، سنعرف بالضبط أي خاصية لم يتم تعيينها بشكل صحيح. الرسالة ستكون شيئًا مثل Expected user.name to be 'John Doe', but received undefined. هذا مفيد للغاية!

نصيحة احترافية

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

2. تقديم رسائل خطأ مفيدة (Useful Error Messages)

هذه هي الفائدة الأهم للتأكيدات الجيدة. عندما يفشل اختبار، فإن رسالة الخطأ هي دليلك الأول لتصحيح المشكلة. استخدام الـ Matcher المناسب يلعب دورًا كبيرًا هنا.

مثال سيء (رسالة خطأ غير مفيدة):

تخيل دالة تتحقق مما إذا كان سلة التسوق تحتوي على منتجات مؤهلة للشحن المجاني.

JavaScript
// src/cart.js
export function hasFreeShippingItems(cart) {
  const freeShippingProducts = ['T-SHIRT-01', 'MUG-02'];
  return cart.some(item => freeShippingProducts.includes(item.sku));
}

// tests/cart.test.js
import { hasFreeShippingItems } from '../src/cart';

it('should detect free shipping items in the cart', () => {
  const cart = [{ sku: 'SOCKS-03' }, { sku: 'MUG-02' }];
  const result = hasFreeShippingItems(cart);

  // Bad: The failure message will be 'Expected true, received false'.
  // This doesn't tell us WHY it's false.
  expect(result).toBe(true);
});

إذا فشل هذا الاختبار، كل ما ستحصل عليه هو: Expected true, received false. هذا لا يساعدك كثيرًا. عليك العودة للكود لفهم المنطق.

مثال جيد (رسالة خطأ مفيدة):

بدلاً من التحقق من قيمة boolean النهائية، يمكننا اختبار النتيجة المباشرة التي نتوقعها، مما يعطي إطار العمل فرصة لتقديم مقارنة مفيدة.

JavaScript
// src/cart.js
export function getFreeShippingItems(cart) {
  const freeShippingProducts = ['T-SHIRT-01', 'MUG-02'];
  return cart.filter(item => freeShippingProducts.includes(item.sku));
}

// tests/cart.test.js
import { getFreeShippingItems } from '../src/cart';

it('should return all free shipping items from the cart', () => {
  const cart = [{ sku: 'SOCKS-03' }, { sku: 'MUG-02' }];
  const result = getFreeShippingItems(cart);

  // Good: If this fails, Vitest will show the difference between the expected and actual arrays.
  // e.g., 'Expected: [{"sku": "MUG-02"}], Received: []'
  expect(result).toEqual([{ sku: 'MUG-02' }]);
  expect(result.length).toBe(1);
});

هنا، إذا أرجعت الدالة مصفوفة فارغة عن طريق الخطأ، فإن رسالة الفشل ستوضح لنا الفرق بين المصفوفة المتوقعة والمصفوفة الفعلية. هذا يوجهنا مباشرة إلى المشكلة.

3. تجنب المنطق المعقد في التأكيدات (Avoid Complex Logic)

يجب أن يكون اختبارك بسيطًا ومباشرًا قدر الإمكان. يجب أن يكون المنطق البرمجي (مثل الحلقات for والشروط if) في الكود الذي تختبره، وليس في الاختبار نفسه. إذا كان اختبارك معقدًا لدرجة أنه قد يحتوي على أخطاء، فمن الذي سيختبر الاختبار؟

مثال سيء (منطق داخل الاختبار):

JavaScript
// src/arrayUtils.js
export function getEvenNumbers(numbers) {
  return numbers.filter(n => n % 2 === 0);
}

// tests/arrayUtils.test.js
import { getEvenNumbers } from '../src/arrayUtils';

it('should return only even numbers', () => {
  const result = getEvenNumbers([1, 2, 3, 4, 5, 6]);

  // Anti-pattern: Logic inside the test.
  // If there's a bug here, the test might give a false positive/negative.
  let allEven = true;
  for (const num of result) {
    if (num % 2 !== 0) {
      allEven = false;
    }
  }
  expect(allEven).toBe(true);
  expect(result.length).toBe(3);
});

لقد أعدنا كتابة منطق التحقق من الأرقام الزوجية داخل الاختبار. هذا يجعل الاختبار هشًا وغير مقروء.

مثال جيد (تأكيد مباشر):

الاختبار يجب أن يتحقق من النتيجة المعروفة مسبقًا.

JavaScript
// tests/arrayUtils.test.js
import { getEvenNumbers } from '../src/arrayUtils';

it('should return only even numbers from a mixed array', () => {
  const input = [1, 2, 3, 4, 5, 6];
  const expectedOutput = [2, 4, 6];

  const result = getEvenNumbers(input);

  // Good: Direct, declarative, and easy to understand.
  // The test clearly states the expected outcome.
  expect(result).toEqual(expectedOutput);
});

هذا النهج يُسمى "الاختبار القائم على الخصائص" (Property-based testing) بشكل مبسط. أنت لا تعيد تنفيذ المنطق، بل تتحقق من أن المخرجات تتطابق مع التوقعات لمجموعة من المدخلات المحددة.

تحذير

كل سطر كود تضيفه إلى اختبارك هو فرصة لإدخال خطأ برمجي جديد. اجعل اختباراتك بسيطة ومباشرة قدر الإمكان.

الخلاصة

كتابة التأكيدات الجيدة هي فن بقدر ما هي علم. إنها مهارة تطورها مع الممارسة. تذكر دائمًا أنك لا تكتب الاختبارات للآلة فقط، بل تكتبها لنفسك في المستقبل ولزملائك في الفريق.

للتلخيص، التأكيد الجيد يجب أن يكون:

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

في الدرس التالي، سنطبق هذه المبادئ في تمرين عملي لتعزيز فهمنا.

قد تُعجبك هذه المشاركات

إرسال تعليق

ليست هناك تعليقات

7627059358572141466

العلامات المرجعية

قائمة العلامات المرجعية فارغة ... قم بإضافة مقالاتك الآن

    البحث