مقدمة: لماذا تخدعك اختباراتك وأنت تصدقها
\n\nتخيل: لقد كتبت 50 اختبار وحدة، غطيت كل if-else، شغّلتها عبر CI — كلها خضراء. بعد أسبوع، ينهار الإنتاج مع خطأ "القسمة على صفر". كيف؟ أليست الاختبارات قد نجحت؟ مألوف؟
مشكلة اختبارات الوحدة التقليدية (التقريبية، بأرقام محددة) هي أنها تتحقق من ما تعرفه بالفعل. أنت تختار الوسائط التي، في رأيك، ستؤدي إلى خطأ. لكن عقل المبرمج يمكن التنبؤ به: كلنا نكتب testDivideByZero وننسى testDivideByNegativeZero أو testDivideByVeryLargeFloat.
هنا يظهر على المسرح الاختبار القائم على الخصائص في PHP — وهي طريقة لا تتحقق من السيناريوهات المحددة، بل تثبت خصائص الكود الخاص بك لآلاف من المدخلات العشوائية. اليوم سنشرح كيفية تطبيق هذا النهج في مشروع PHP الخاص بك باستخدام Eris و PHPUnit. ستتعلم كيفية العثور على الأخطاء التي تفوتها الاختبارات العادية، ولماذا هذا ليس سحرًا بل هندسة.
\n\nما هو الاختبار القائم على الخصائص؟ (ولماذا ليس عشوائيًا)
\n\nاختبار الوحدة العادي يبدو هكذا: assert(2 + 2 === 4). أنت تتحقق من عبارة واحدة محددة. الاختبار القائم على الخصائص (PBT) يعمل بشكل مختلف: أنت تصوغ خاصية — عبارة يجب أن تكون صحيحة لـ أي مدخلات صحيحة.
مثال على خاصية لدالة الجمع:
\n- \n
- التبديلية:
a + b === b + aلأي أرقام. \n - الاستقرارية: التطبيق المتكرر للدالة لا يغير النتيجة. \n
- الثبات: النتيجة دائمًا موجبة (إذا كانت هذه خاصية). \n
يولد PBT مئات القيم العشوائية (بما في ذلك القيم الحدودية، الصفرية، السالبة، NaN) ويتحقق مما إذا كانت الخاصية قد انتهكت. إذا تم العثور على مثال مضاد — يفشل الاختبار مع بيانات إدخال محددة تسببت في العطل. تحصل ليس فقط على "فشل الاختبار"، بل على خطأ قابل للتكرار.
\n\nفي PHP، هذه التقنية مفيدة بشكل خاص بسبب الكتابة الديناميكية: أنت لا تعرف أبدًا أي نوع سيصل إلى الدالة في الواقع.
\n\nأدوات الاختبار القائم على الخصائص في PHP
\n\nالأداة الرئيسية هي مكتبة Eris (github.com/giorgiosironi/eris). تتكامل مع PHPUnit وتوفر مولدات بيانات عشوائية. البديل هو الإمكانيات المدمجة في PHPUnit 10+ (لكنها لا تزال غير ناضجة). سنستخدم Eris لأنها أداة ناضجة وموثقة.
\n\nالتثبيت عبر Composer:
\ncomposer require --dev giorgiosironi/eris\n\nسيكون Faker مفيدًا أيضًا لتوليد الهياكل المعقدة (البريد الإلكتروني، الأسماء، العناوين).
\n\ncomposer require --dev fakerphp/faker\n\nكتابة أول اختبار قائم على الخصائص: كسر الترتيب
\n\nلنفترض أن لدينا دالة sortArray() تستخدم quicksort. لنكتب خاصية: "يجب ألا يحتوي المصفوفة بعد الترتيب على عناصر أصغر من العنصر السابق".
use Eris\\TestTrait;\nuse Eris\\Generator\\IntegerGenerator;\nuse PHPUnit\\Framework\\TestCase;\n\nclass SortTest extends TestCase\n{\n use TestTrait;\n\n public function testSortedArrayProperty(): void\n {\n $this->forAll(\n Generator\\seq(Generator\\int())\n )->then(function (array $input) {\n $sorted = sortArray($input);\n for ($i = 0; $i < count($sorted) - 1; $i++) {\n $this->assertLessThanOrEqual(\n $sorted[$i + 1],\n $sorted[$i],\n "المصفوفة غير مرتبة: " . json_encode($sorted)\n );\n }\n });\n }\n}\n\n\nتشغيل