الاختبارات و TDD

أساسيات PHPUnit

18 دقيقة الدرس 4 من 35

أساسيات PHPUnit

PHPUnit هو إطار الاختبار الأكثر شيوعًا لتطبيقات PHP. يوفر مجموعة شاملة من الأدوات لكتابة وتشغيل اختبارات الوحدة، مما يجعله المعيار الصناعي لاختبار PHP. سواء كنت تبني سكريبت بسيط أو تطبيق Laravel كبير، فإن PHPUnit هو أداة الاختبار المفضلة لديك.

ما هو PHPUnit؟

PHPUnit هو إطار اختبار موجه للمبرمجين لـ PHP أنشأه Sebastian Bergmann. إنه عضو في عائلة xUnit من أطر الاختبار ويوفر:

  • مجموعة غنية من التأكيدات للتحقق من سلوك الكود
  • قدرات تنظيم وتجميع الاختبار
  • تركيبات الاختبار للإعداد والتنظيف
  • تحليل تغطية الكود
  • كائنات وهمية لعزل الاختبارات
  • موفرو البيانات لاختبار سيناريوهات متعددة

المعيار الصناعي: يُستخدم PHPUnit من قبل مشاريع PHP الرئيسية بما في ذلك Laravel و Symfony و WordPress و Drupal وآلاف المشاريع الأخرى في جميع أنحاء العالم.

التثبيت

يمكن تثبيت PHPUnit عبر Composer، مدير اعتماديات PHP:

# تثبيت PHPUnit كاعتمادية تطوير composer require --dev phpunit/phpunit # التحقق من التثبيت ./vendor/bin/phpunit --version

لمشاريع Laravel، يأتي PHPUnit مثبتًا مسبقًا:

# Laravel يتضمن بالفعل PHPUnit php artisan test # أو استخدم PHPUnit مباشرة ./vendor/bin/phpunit

التكوين

يستخدم PHPUnit ملف تكوين XML (عادة phpunit.xml) لتحديد إعدادات الاختبار:

<?xml version="1.0" encoding="UTF-8"?> <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd" bootstrap="vendor/autoload.php" colors="true"> <testsuites> <testsuite name="Unit"> <directory>tests/Unit</directory> </testsuite> <testsuite name="Feature"> <directory>tests/Feature</directory> </testsuite> </testsuites> <coverage> <include> <directory suffix=".php">./app</directory> </include> </coverage> </phpunit>

كتابة أول اختبار لك

اختبارات PHPUnit هي فئات تمتد من PHPUnit\Framework\TestCase:

<?php // tests/Unit/CalculatorTest.php use PHPUnit\Framework\TestCase; class CalculatorTest extends TestCase { public function test_addition() { // الترتيب $calculator = new Calculator(); // التنفيذ $result = $calculator->add(2, 3); // التأكيد $this->assertEquals(5, $result); } public function test_subtraction() { $calculator = new Calculator(); $result = $calculator->subtract(10, 4); $this->assertEquals(6, $result); } } ?>

اصطلاح التسمية: يجب أن تبدأ طرق الاختبار بـ test أو تستخدم تعليق @test. يجب أن تنتهي أسماء فئات الاختبار بـ Test.

تشغيل الاختبارات

نفذ الاختبارات باستخدام أمر PHPUnit:

# تشغيل جميع الاختبارات ./vendor/bin/phpunit # تشغيل ملف اختبار محدد ./vendor/bin/phpunit tests/Unit/CalculatorTest.php # تشغيل طريقة اختبار محددة ./vendor/bin/phpunit --filter test_addition # التشغيل مع تقرير التغطية ./vendor/bin/phpunit --coverage-html coverage # تشغيل اختبارات في مجموعة محددة ./vendor/bin/phpunit --testsuite Unit

التأكيدات

التأكيدات هي قلب PHPUnit. تتحقق من أن الكود الخاص بك يتصرف بشكل صحيح:

تأكيدات المساواة

<?php // تأكيد أن قيمتين متساويتان $this->assertEquals(expected, actual); $this->assertEquals(5, $calculator->add(2, 3)); // تأكيد متطابق (مقارنة صارمة مع ===) $this->assertSame(expected, actual); $this->assertSame('5', $result); // النوع مهم! // تأكيد غير متساو $this->assertNotEquals(5, $calculator->add(2, 2)); ?>

تأكيدات منطقية

<?php // تأكيد صحيح $this->assertTrue($user->isActive()); // تأكيد خطأ $this->assertFalse($user->isBanned()); // تأكيد null $this->assertNull($user->deletedAt); // تأكيد ليس null $this->assertNotNull($user->createdAt); ?>

تأكيدات النوع

<?php // تأكيد مثيل من فئة $this->assertInstanceOf(User::class, $user); // تأكيد هو مصفوفة $this->assertIsArray($users); // تأكيد هو نص $this->assertIsString($name); // تأكيد هو رقمي $this->assertIsNumeric($age); ?>

تأكيدات النصوص

<?php // تأكيد أن النص يحتوي على نص فرعي $this->assertStringContainsString('hello', $message); // تأكيد أن النص يبدأ بـ $this->assertStringStartsWith('http://', $url); // تأكيد أن النص ينتهي بـ $this->assertStringEndsWith('.com', $domain); // تأكيد أن النص يطابق regex $this->assertMatchesRegularExpression('/^[A-Z]/', $name); ?>

تأكيدات المصفوفة

<?php // تأكيد أن المصفوفة لها مفتاح $this->assertArrayHasKey('email', $user); // تأكيد أن المصفوفة تحتوي على قيمة $this->assertContains('admin', $roles); // تأكيد عدد المصفوفة $this->assertCount(3, $users); // تأكيد مصفوفة فارغة $this->assertEmpty($errors); ?>

تأكيدات الاستثناءات

<?php public function test_division_by_zero_throws_exception() { $this->expectException(DivisionByZeroError::class); $this->expectExceptionMessage('Division by zero'); $calculator = new Calculator(); $calculator->divide(10, 0); } ?>

مهم: يجب استدعاء expectException() قبل الكود الذي يرمي الاستثناء، وليس بعده!

تركيبات الاختبار: الإعداد والتنظيف

يوفر PHPUnit خطافات لإعداد وتنظيف بيئات الاختبار:

<?php class UserTest extends TestCase { private $user; // يعمل مرة واحدة قبل جميع الاختبارات في الفئة public static function setUpBeforeClass(): void { // تهيئة الموارد المشتركة DB::connect(); } // يعمل قبل كل طريقة اختبار protected function setUp(): void { parent::setUp(); $this->user = new User([ 'name' => 'John Doe', 'email' => 'john@example.com' ]); } // يعمل بعد كل طريقة اختبار protected function tearDown(): void { $this->user = null; parent::tearDown(); } // يعمل مرة واحدة بعد جميع الاختبارات في الفئة public static function tearDownAfterClass(): void { DB::disconnect(); } public function test_user_has_name() { $this->assertEquals('John Doe', $this->user->name); } } ?>

موفرو البيانات

يتيح لك موفرو البيانات تشغيل نفس الاختبار بمدخلات مختلفة:

<?php class MathTest extends TestCase { /** * @dataProvider additionProvider */ public function test_addition($a, $b, $expected) { $calculator = new Calculator(); $result = $calculator->add($a, $b); $this->assertEquals($expected, $result); } public static function additionProvider(): array { return [ 'positive numbers' => [2, 3, 5], 'negative numbers' => [-2, -3, -5], 'mixed numbers' => [-2, 5, 3], 'with zero' => [0, 5, 5], ]; } } ?>

تعمل طريقة الاختبار الواحدة هذه أربع مرات بمجموعات بيانات مختلفة!

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

اعتماديات الاختبار

في بعض الأحيان تعتمد الاختبارات على نتائج اختبارات أخرى:

<?php class StackTest extends TestCase { public function test_push_and_pop() { $stack = []; $stack[] = 'foo'; $this->assertSame('foo', array_pop($stack)); $this->assertEmpty($stack); return $stack; } /** * @depends test_push_and_pop */ public function test_empty_after_pop($stack) { $this->assertEmpty($stack); } } ?>

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

تخطي وإكمال الاختبارات

<?php class FeatureTest extends TestCase { public function test_requires_database() { if (!extension_loaded('pdo_mysql')) { $this->markTestSkipped( 'MySQL extension not available' ); } // كود الاختبار هنا } public function test_incomplete_feature() { $this->markTestIncomplete( 'This test is not yet implemented' ); } } ?>

اختبار الطرق المحمية/الخاصة

بينما يجب عليك عمومًا اختبار الواجهات العامة، في بعض الأحيان تحتاج إلى اختبار الطرق الخاصة:

<?php class UserTest extends TestCase { public function test_private_validation() { $user = new User(); // استخدم الانعكاس للوصول إلى طريقة خاصة $reflection = new ReflectionClass($user); $method = $reflection->getMethod('validateEmail'); $method->setAccessible(true); $result = $method->invoke($user, 'invalid-email'); $this->assertFalse($result); } } ?>

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

مخرجات الاختبار

يوفر PHPUnit مخرجات اختبار تفصيلية:

PHPUnit 10.5.5 by Sebastian Bergmann and contributors. Runtime: PHP 8.2.0 Configuration: phpunit.xml ..F.. 5 / 5 (100%) Time: 00:00.123, Memory: 10.00 MB There was 1 failure: 1) CalculatorTest::test_division Failed asserting that 2 matches expected 3. tests/Unit/CalculatorTest.php:25 FAILURES! Tests: 5, Assertions: 8, Failures: 1.

الرموز في المخرجات:

  • . - اختبار ناجح
  • F - تأكيد فاشل
  • E - خطأ (استثناء مرمي)
  • S - اختبار متخطى
  • I - اختبار غير مكتمل

تغطية الكود

يمكن لـ PHPUnit تحليل أي أسطر من الكود يتم تنفيذها بواسطة الاختبارات:

# توليد تقرير تغطية HTML ./vendor/bin/phpunit --coverage-html coverage # توليد ملخص تغطية نصي ./vendor/bin/phpunit --coverage-text # طلب الحد الأدنى من التغطية ./vendor/bin/phpunit --coverage-text --coverage-filter app

المتطلبات: تتطلب تغطية الكود تثبيت امتداد Xdebug أو PCOV PHP.

تمرين عملي

أنشئ فئة StringHelper واكتب اختبارات PHPUnit لها:

<?php class StringHelper { public static function reverse(string $str): string { return strrev($str); } public static function capitalize(string $str): string { return ucfirst($str); } public static function truncate(string $str, int $length): string { if (strlen($str) <= $length) { return $str; } return substr($str, 0, $length) . '...'; } } ?>

مهمتك:

  1. أنشئ StringHelperTest.php
  2. اكتب على الأقل 2 اختبارات لكل طريقة
  3. اختبر الحالات الحدية (نصوص فارغة، نصوص طويلة جدًا)
  4. استخدم موفري البيانات لطريقة truncate
  5. شغل الاختبارات وتأكد من تغطية 100% للكود

الخلاصة

PHPUnit هو إطار اختبار قوي ومرن لـ PHP. مع مكتبة التأكيدات الغنية وميزات تنظيم الاختبار وأدوات مثل موفري البيانات وتغطية الكود، يجعل PHPUnit كتابة وصيانة الاختبارات سهلة. في الدرس التالي، سنستكشف Jest، المكافئ JavaScript لـ PHPUnit.

النقاط الرئيسية:

  • PHPUnit هو المعيار الصناعي لاختبار PHP
  • تمتد الاختبارات من TestCase والطرق تبدأ بـ test
  • استخدم setUp/tearDown لتركيبات الاختبار
  • مكتبة التأكيدات الغنية تغطي معظم احتياجات الاختبار
  • موفرو البيانات يتيحون اختبار سيناريوهات متعددة بكفاءة
  • تغطية الكود تساعد في تحديد الكود غير المختبر