الاختبار القائم على الخصائص في TypeScript: كيفية اكتشاف الأخطاء التي لم تبحث عنها

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: لماذا لا تحميك اختبارات الوحدة من كل شيء

\n\n

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

\n\n

اختبار TypeScript التقليدي باستخدام الأمثلة (example-based testing) يتحقق فقط من السيناريوهات التي فكرت فيها. ماذا لو كان الخطأ مختبئًا في تركيبة لم تفكر بها؟ هنا يأتي دور الاختبار القائم على الخصائص (property-based testing) - نهج يقوم تلقائيًا بتوليد آلاف من بيانات الإدخال العشوائية ويتحقق من الثوابت (invariants) في الكود الخاص بك.

\n\n

في هذه المقالة، سنشرح كيفية تطبيق الاختبار القائم على الخصائص في TypeScript باستخدام مكتبة fast-check، وما هي الخصائص التي يجب التحقق منها، وكيف يغير هذا النهج في التطوير.

\n\n

ما هو الاختبار القائم على الخصائص؟

\n\n

بدلاً من كتابة:

\n
it('يرتب [3, 1, 2] ← [1, 2, 3]', () => {\n  expect(sort([3, 1, 2])).toEqual([1, 2, 3]);\n});\n
\n\n

تصف خاصية (property) - تأكيد يجب أن يكون صحيحًا لجميع بيانات الإدخال الصحيحة:

\n
it('المصفوفة المرتبة لا تغير ترتيب العناصر', () => {\n  fc.assert(\n    fc.property(fc.array(fc.integer()), (arr) => {\n      const sorted = sort(arr);\n      // الخاصية 1: الطول محفوظ\n      expect(sorted.length).toBe(arr.length);\n      // الخاصية 2: كل عنصر من المصفوفة الأصلية موجود\n      // الخاصية 3: المصفوفة مرتبة تصاعديًا\n      for (let i = 1; i < sorted.length; i++) {\n        expect(sorted[i - 1]).toBeLessThanOrEqual(sorted[i]);\n      }\n    })\n  );\n});\n
\n\n

مكتبة fast-check ستقوم بنفسها بتوليد مئات المصفوفات العشوائية (بما في ذلك الفارغة، والتي تحتوي على NaN، والأرقام الكسرية، والقيم الضخمة) وتشغيل اختبارك على كل منها. إذا فشل أي منها - ستعرف بوجود مشكلة.

\n\n

لماذا هذا ضروري تحديدًا في TypeScript؟

\n\n

TypeScript يوفر كتابة ثابتة (static typing)، لكنه لا يضمن صحة المنطق. الاختبار القائم على الخصائص في TypeScript يسد الفجوة بين "الأنواع متطابقة" و"الكود يعمل بشكل صحيح". هذا مفيد بشكل خاص لـ:

\n\n
    \n
  • دوال تحويل البيانات (map, filter, reduce)
  • \n
  • المحللات (Parsers) والمتحققين (Validators)
  • \n
  • الخوارزميات ذات المنطق غير البديهي
  • \n
  • الدوال التي تتعامل مع القيم الحدودية
  • \n
\n\n

مكتبة fast-check: بداية سريعة

\n\n

قم بتثبيت fast-check في مشروعك:

\n
npm install --save-dev fast-check\n
\n\n

الصيغة الأساسية:

\n
import fc from 'fast-check';\n\ndescribe('خصائص دالة reverse', () => {\n  it('reverse(reverse(arr)) === arr', () => {\n    fc.assert(\n      fc.property(fc.array(fc.anything()), (arr) => {\n        expect(reverse(reverse(arr))).toEqual(arr);\n      })\n    );\n  });\n});\n
\n\n

هنا fc.array(fc.anything()) هو مولد لمصفوفات عشوائية. يمكنك دمج المولدات:

\n
    \n
  • fc.integer() - أعداد صحيحة
  • \n
  • fc.string() - نصوص
  • \n
  • fc.record({ name: fc.string(), age: fc.integer({ min: 0, max: 120 }) }) - كائنات
  • \n
  • fc.oneof(...) - اختيار عشوائي من عدة مولدات
  • \n
\n\n

ما هي الخصائص التي يجب التحقق منها؟ 5 أمثلة عملية

\n\n

1. الثبات (الانعكاسية)

\n

أي عملية لها عملية عكسية يجب أن تكون قابلة للعكس:

\n
it('التسلسل وإلغاء التسلسل لـ JSON', () => {\n  fc.assert(\n    fc.property(fc.object(), (obj) => {\n      const json = JSON.stringify(obj);\n      const parsed = JSON.parse(json);\n      expect(parsed).toEqual(obj);\n    })\n  );\n});\n<                

المدونات

توصيات الكتب