الاختبار في لارافل
تم بناء لارافل مع وضع الاختبار في الاعتبار وتتضمن دعمًا للاختبار باستخدام PHPUnit بشكل افتراضي. يضمن الاختبار أن تطبيقك يعمل كما هو متوقع ويساعد في منع الأخطاء عند إجراء التغييرات. في هذا الدرس، سنستكشف ميزات الاختبار القوية في لارافل.
لماذا يهم الاختبار
يوفر الاختبار عدة فوائد حاسمة:
- الثقة: إجراء التغييرات مع العلم أنك لن تكسر الوظائف الموجودة
- التوثيق: الاختبارات بمثابة توثيق حي لكيفية عمل الكود
- إعادة الهيكلة: تحسين بنية الكود بأمان دون خوف
- منع الأخطاء: اكتشاف المشكلات قبل وصولها إلى الإنتاج
- تصميم أفضل: كتابة كود قابل للاختبار يؤدي إلى بنية أفضل
ملاحظة: تأتي لارافل مع إعداد مسبق لـ PHPUnit وتتضمن ملف phpunit.xml في جذر المشروع. يتم تخزين الاختبارات في دليل tests/.
أنواع الاختبارات في لارافل
تدعم لارافل نوعين رئيسيين من الاختبارات:
1. اختبارات الميزات (Feature Tests)
تختبر اختبارات الميزات (تسمى أيضًا اختبارات التكامل) أجزاء أكبر من تطبيقك، بما في ذلك كيفية تفاعل عدة فئات مع بعضها البعض وحتى إجراء طلبات HTTP إلى تطبيقك:
<?php
namespace Tests\Feature;
use Tests\TestCase;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
class UserRegistrationTest extends TestCase
{
use RefreshDatabase;
public function test_user_can_register()
{
$response = $this->post('/register', [
'name' => 'John Doe',
'email' => 'john@example.com',
'password' => 'password123',
'password_confirmation' => 'password123',
]);
$response->assertRedirect('/dashboard');
$this->assertDatabaseHas('users', [
'email' => 'john@example.com',
]);
}
public function test_registration_requires_valid_email()
{
$response = $this->post('/register', [
'name' => 'John Doe',
'email' => 'invalid-email',
'password' => 'password123',
'password_confirmation' => 'password123',
]);
$response->assertSessionHasErrors('email');
}
}
2. اختبارات الوحدة (Unit Tests)
تركز اختبارات الوحدة على اختبار أجزاء صغيرة ومعزولة من الكود، عادةً طرق أو فئات فردية:
<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
use App\Services\PriceCalculator;
class PriceCalculatorTest extends TestCase
{
public function test_calculates_price_with_tax()
{
$calculator = new PriceCalculator();
$price = $calculator->calculateWithTax(100, 0.15);
$this->assertEquals(115, $price);
}
public function test_applies_discount_correctly()
{
$calculator = new PriceCalculator();
$price = $calculator->applyDiscount(100, 10);
$this->assertEquals(90, $price);
}
public function test_throws_exception_for_negative_price()
{
$this->expectException(\InvalidArgumentException::class);
$calculator = new PriceCalculator();
$calculator->calculateWithTax(-100, 0.15);
}
}
إنشاء الاختبارات
استخدم أوامر Artisan لإنشاء ملفات الاختبار:
# إنشاء اختبار ميزة
php artisan make:test UserProfileTest
# إنشاء اختبار وحدة
php artisan make:test CalculatorTest --unit
# إنشاء اختبار بطرق محددة
php artisan make:test PostTest --pest # لإطار عمل Pest
تشغيل الاختبارات
قم بتنفيذ اختباراتك باستخدام PHPUnit أو Artisan:
# تشغيل جميع الاختبارات
php artisan test
# تشغيل ملف اختبار محدد
php artisan test tests/Feature/UserTest.php
# تشغيل طريقة اختبار محددة
php artisan test --filter test_user_can_login
# تشغيل الاختبارات بشكل متوازي (أسرع)
php artisan test --parallel
# تشغيل مع تقرير التغطية
php artisan test --coverage
اختبار قاعدة البيانات
توفر لارافل عدة traits وطرق لاختبار قاعدة البيانات:
Trait RefreshDatabase
يقوم هذا الـ trait بترحيل وإعادة تعيين قاعدة البيانات بعد كل اختبار:
<?php
namespace Tests\Feature;
use Tests\TestCase;
use App\Models\Post;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
class PostTest extends TestCase
{
use RefreshDatabase;
public function test_user_can_create_post()
{
$user = User::factory()->create();
$this->actingAs($user)
->post('/posts', [
'title' => 'My First Post',
'content' => 'This is the content.',
]);
$this->assertDatabaseHas('posts', [
'title' => 'My First Post',
'user_id' => $user->id,
]);
}
public function test_post_belongs_to_user()
{
$user = User::factory()->create();
$post = Post::factory()->create(['user_id' => $user->id]);
$this->assertInstanceOf(User::class, $post->user);
$this->assertEquals($user->id, $post->user->id);
}
}
نصيحة: استخدم trait RefreshDatabase بدلاً من DatabaseMigrations لاختبارات أسرع. يستخدم المعاملات عندما يكون ذلك ممكنًا، ويقوم بتشغيل الترحيلات فقط عند الضرورة.
اختبار HTTP
توفر لارافل واجهة برمجة تطبيقات سلسة لاختبار طلبات واستجابات HTTP:
<?php
namespace Tests\Feature;
use Tests\TestCase;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
class ApiTest extends TestCase
{
use RefreshDatabase;
public function test_api_returns_users_list()
{
User::factory()->count(3)->create();
$response = $this->getJson('/api/users');
$response->assertStatus(200)
->assertJsonCount(3, 'data')
->assertJsonStructure([
'data' => [
'*' => ['id', 'name', 'email', 'created_at']
]
]);
}
public function test_api_requires_authentication()
{
$response = $this->getJson('/api/profile');
$response->assertUnauthorized();
}
public function test_authenticated_user_can_access_profile()
{
$user = User::factory()->create();
$response = $this->actingAs($user)
->getJson('/api/profile');
$response->assertOk()
->assertJson([
'email' => $user->email,
]);
}
}
التأكيدات الشائعة
توفر لارافل العديد من طرق التأكيد للاختبار:
<?php
// تأكيدات الاستجابة
$response->assertOk(); // حالة 200
$response->assertCreated(); // حالة 201
$response->assertNotFound(); // حالة 404
$response->assertForbidden(); // حالة 403
$response->assertUnauthorized(); // حالة 401
$response->assertRedirect('/home'); // تأكيد إعادة التوجيه
// تأكيدات JSON
$response->assertJson(['key' => 'value']);
$response->assertJsonFragment(['name' => 'John']);
$response->assertJsonMissing(['secret' => 'hidden']);
$response->assertJsonPath('data.0.id', 1);
// تأكيدات قاعدة البيانات
$this->assertDatabaseHas('users', ['email' => 'test@example.com']);
$this->assertDatabaseMissing('users', ['email' => 'deleted@example.com']);
$this->assertDatabaseCount('posts', 5);
$this->assertSoftDeleted('posts', ['id' => 1]);
// تأكيدات المصادقة
$this->assertAuthenticated();
$this->assertGuest();
$this->assertAuthenticatedAs($user);
// تأكيدات الجلسة
$response->assertSessionHas('key', 'value');
$response->assertSessionHasErrors(['email']);
$response->assertSessionMissing('key');
// تأكيدات العرض
$response->assertViewIs('posts.index');
$response->assertViewHas('posts');
$response->assertViewHasAll(['posts', 'categories']);
المحاكاة والتزييف (Mocking & Faking)
توفر لارافل طرقًا مريحة لمحاكاة الخدمات والتبعيات الخارجية:
<?php
namespace Tests\Feature;
use Tests\TestCase;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Notification;
use App\Mail\WelcomeEmail;
use App\Jobs\ProcessOrder;
use App\Models\User;
class MockingTest extends TestCase
{
public function test_welcome_email_is_sent()
{
Mail::fake();
$user = User::factory()->create();
// تشغيل الكود الذي يرسل البريد الإلكتروني
$user->sendWelcomeEmail();
Mail::assertSent(WelcomeEmail::class, function ($mail) use ($user) {
return $mail->hasTo($user->email);
});
}
public function test_job_is_dispatched()
{
Queue::fake();
// تشغيل الكود الذي يرسل المهمة
ProcessOrder::dispatch($orderId = 123);
Queue::assertPushed(ProcessOrder::class, function ($job) use ($orderId) {
return $job->orderId === $orderId;
});
}
public function test_file_upload_stores_file()
{
Storage::fake('public');
$response = $this->post('/upload', [
'file' => UploadedFile::fake()->image('photo.jpg')
]);
Storage::disk('public')->assertExists('photos/photo.jpg');
}
}
تحذير: عند استخدام التزييف (fakes)، لن يتم تنفيذ الكود الفعلي (إرسال البريد الإلكتروني، إرسال المهام، إلخ). استخدم التزييف لاختبار أن الكود يتم استدعاؤه بشكل صحيح، وليس لاختبار التنفيذ نفسه.
قواعد بيانات الاختبار
قم بتكوين قاعدة بيانات منفصلة للاختبار في ملف phpunit.xml:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit>
<php>
<env name="APP_ENV" value="testing"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
</php>
</phpunit>
تمرين 1: اختبار الميزة
أنشئ اختبار ميزة لإنشاء منشور مدونة:
- قم بتشغيل:
php artisan make:test PostCreationTest
- اختبر أن المستخدمين المصادقين يمكنهم إنشاء منشورات
- اختبر أن الضيوف يتم إعادة توجيههم إلى تسجيل الدخول
- اختبر أن التحقق من الصحة يعمل (العنوان مطلوب)
- اختبر أن المنشور يظهر في قاعدة البيانات
تمرين 2: اختبار الوحدة
أنشئ اختبار وحدة لحاسبة الخصم:
- أنشئ فئة
DiscountCalculator مع طريقة calculate()
- قم بتشغيل:
php artisan make:test DiscountCalculatorTest --unit
- اختبر خصومات النسبة المئوية (10٪ من 100 دولار = 90 دولار)
- اختبر خصومات المبلغ الثابت (10 دولارات من 100 دولار = 90 دولار)
- اختبر أن الخصومات لا يمكن أن تتجاوز السعر الأصلي
تمرين 3: اختبار HTTP
أنشئ اختبار API شامل:
- اختبر GET /api/products يعيد قائمة المنتجات
- اختبر POST /api/products ينشئ منتجًا جديدًا (مصادق)
- اختبر PUT /api/products/{id} يحدث منتجًا
- اختبر DELETE /api/products/{id} يحذف منتجًا
- اختبر أن جميع النقاط النهائية تتطلب المصادقة
- استخدم المصانع لإنشاء بيانات الاختبار
أفضل الممارسات
- اختبر شيئًا واحدًا: يجب أن تتحقق كل طريقة اختبار من سلوك محدد واحد
- استخدم أسماء وصفية: يجب أن تصف أسماء طرق الاختبار ما تختبره
- ترتيب-تنفيذ-تأكيد: هيكل الاختبارات في ثلاث مراحل: الإعداد، التنفيذ، التحقق
- استخدم المصانع: قم بإنشاء بيانات الاختبار باستخدام المصانع بدلاً من الإنشاء اليدوي
- اختبارات سريعة: حافظ على سرعة الاختبارات باستخدام قواعد البيانات في الذاكرة ومحاكاة الخدمات الخارجية
- اختبارات مستقلة: يجب أن يكون كل اختبار قادرًا على العمل بشكل مستقل
- لا تختبر الإطار: ركز على اختبار كود التطبيق الخاص بك، وليس لارافل نفسها
الخلاصة
في هذا الدرس، تعلمت:
- الفرق بين اختبارات الميزات واختبارات الوحدة
- كيفية إنشاء وتشغيل الاختبارات في لارافل
- اختبار قاعدة البيانات باستخدام trait RefreshDatabase
- اختبار HTTP وتأكيدات الاستجابة
- كيفية محاكاة الخدمات الخارجية باستخدام التزييف
- طرق التأكيد الشائعة لسيناريوهات مختلفة
- أفضل الممارسات لكتابة اختبارات قابلة للصيانة
الاختبار مهارة أساسية لتطوير لارافل الاحترافي. ابدأ باختبار الميزات الحرجة، ثم قم بزيادة التغطية تدريجيًا. الكود المُختبر جيدًا يؤدي إلى تطبيقات أكثر موثوقية وقابلة للصيانة.