Testing في Laravel: كيف تفكّر بالاختبارات قبل ما تكتب أول Test
أغلب المطورين يتعامل مع الاختبارات كمرحلة لاحقة:
المشروع يشتغل أولاً… وبعدها نحاول نضيف Tests.
لكن مع الوقت تكتشف إن المشكلة مو في كتابة الاختبار،
المشكلة في فهم ماذا يجب اختباره أصلًا.
Laravel لا يقدّم PHPUnit كأداة إضافية،
بل يبني حولها نظام Testing كامل مرتبط بدورة حياة التطبيق نفسها.
ما هو PHPUnit داخل Laravel فعليًا؟
PHPUnit هو محرك الاختبارات،
أما Laravel فهو الطبقة التي تجعل اختبار التطبيق ممكن بدون تعقيد.
عند تنفيذ:
php artisan test
Laravel يقوم بـ:
- تشغيل PHPUnit
- إنشاء Application Instance
- تحميل Service Container
- تهيئة البيئة الخاصة بالاختبار
بمعنى أن الاختبار يعمل داخل نسخة حقيقية من التطبيق.
أنواع الاختبارات في Laravel
Laravel يقسم الاختبارات منطقيًا إلى مستويين أساسيين.
1) Unit Tests
Unit Test يختبر جزء صغير جدًا من الكود بدون تشغيل النظام بالكامل.
مثال:
- Class
- Service
- Helper
- Logic منفصل
مثال:
class PriceCalculator
{
public function total($price, $tax)
{
return $price + ($price * $tax);
}
}
الاختبار:
test('calculate total price', function () {
$calc = new PriceCalculator();
expect($calc->total(100, 0.15))
->toBe(115);
});
هنا:
- لا Database
- لا HTTP
- لا Laravel Models
فقط منطق برمجي.
هدفه السرعة والعزل.
2) Feature Tests
Feature Test يختبر سلوك النظام بالكامل.
مثلاً:
- Request → Controller → Model → Database → Response
مثال:
test('user can create post', function () {
$user = User::factory()->create();
$response = $this->actingAs($user)
->post('/posts', [
'title' => 'Test Post'
]);
$response->assertStatus(302);
$this->assertDatabaseHas('posts', [
'title' => 'Test Post'
]);
});
هنا الاختبار يشمل:
- Authentication
- Routing
- Validation
- Database
هذا اختبار سلوك التطبيق وليس دالة واحدة.
الفرق الحقيقي بين Unit و Feature
| Unit |
Feature |
| سريع جدًا |
أبطأ |
| منطق فقط |
النظام كامل |
| بدون Database |
يستخدم Database |
| عزل كامل |
تكامل حقيقي |
المشاريع الصحية تستخدم الاثنين.
Unit يمنع الأخطاء المنطقية،
Feature يمنع انهيار النظام.
اختبار HTTP Layer
Laravel يسمح بمحاكاة الطلبات بدون تشغيل سيرفر فعلي.
$this->get('/dashboard')
->assertOk();
أو:
$this->postJson('/api/login', [
'email' => '[email protected]',
'password' => 'secret'
])->assertJsonStructure([
'token'
]);
لا يوجد Browser هنا،
فقط محاكاة Kernel داخليًا.
Testing Database بدون تخريب البيانات
Laravel يوفر Trait مهم:
use RefreshDatabase;
هذا يقوم بـ:
- إعادة Migration قبل كل Test
- تنظيف البيانات تلقائيًا
بالتالي كل اختبار يبدأ ببيئة نظيفة.
Factories: أساس الاختبارات الواقعية
بدل إنشاء بيانات يدويًا:
User::create([...]);
نستخدم Factory:
User::factory()->create();
أو:
Post::factory()
->count(5)
->for(User::factory())
->create();
Factories تجعل الاختبار قريب من الواقع.
Mocking داخل Laravel
أحيانًا لا تريد استدعاء خدمة حقيقية.
مثلاً API خارجي.
Laravel يدعم Mock بسهولة:
$this->mock(PaymentService::class, function ($mock) {
$mock->shouldReceive('charge')
->once()
->andReturn(true);
});
الاختبار هنا يتحقق من السلوك،
بدون تنفيذ الاتصال الحقيقي.
اختبار Events و Jobs و Notifications
Laravel يسمح بتجميد الأنظمة الجانبية.
مثال Events:
Event::fake();
OrderCreated::dispatch();
Event::assertDispatched(OrderCreated::class);
Jobs:
Queue::fake();
ProcessOrder::dispatch();
Queue::assertPushed(ProcessOrder::class);
Notifications:
Notification::fake();
Notification::assertSentTo(
$user,
OrderNotification::class
);
بهذا الشكل تختبر التدفق بدون تنفيذ فعلي.
Pest vs PHPUnit
Laravel يدعم الاثنين.
PHPUnit:
Pest:
- Functional style
- أقل Boilerplate
- قراءة أسرع
كلاهما يستخدم نفس المحرك.
الاختلاف في أسلوب الكتابة فقط.
متى تعرف أن مشروعك يحتاج Testing فعلاً؟
عادة عند حدوث أحد هذه الأمور:
- الخوف من تعديل كود قديم
- ظهور Bugs بعد كل Feature
- Refactoring صعب
- اعتماد كامل على الاختبار اليدوي
الاختبارات ليست ضمان خلو الأخطاء،
بل ضمان أن التغييرات لا تكسر ما كان يعمل.
الخلاصة
Testing في Laravel ليس كتابة Assertions،
بل بناء طبقة ثقة فوق التطبيق.
Unit Tests تحمي المنطق،
Feature Tests تحمي السلوك،
وMocking يسمح بعزل العالم الخارجي.
المشروع الذي يملك اختبارات جيدة،
يمكن تطويره بلا خوف.