أساسيات اختبار الواجهات البرمجية
الاختبار أمر بالغ الأهمية لبناء واجهات برمجية موثوقة وقابلة للصيانة. يضمن الاختبار الشامل أن نقاط النهاية الخاصة بك تتصرف بشكل صحيح، وتتعامل مع الحالات الحدية بسلاسة، وتبقى مستقرة مع تطور قاعدة التعليمات البرمجية. في هذا الدرس، سنستكشف اختبار الواجهات البرمجية باستخدام PHPUnit وميزات الاختبار في Laravel وتأكيدات JSON ومصانع الاختبار واستراتيجيات اختبار قاعدة البيانات.
لماذا يهم اختبار الواجهة البرمجية
الواجهات البرمجية غير المختبرة تؤدي إلى أخطاء الإنتاج وثغرات الأمان وتجربة مطور سيئة:
- الموثوقية: الاختبارات التلقائية تلتقط الأخطاء قبل وصولها إلى الإنتاج
- الثقة: الاختبارات تمكن من إعادة هيكلة آمنة وتطوير الميزات
- التوثيق: الاختبارات تعمل كتوثيق قابل للتنفيذ للسلوك المتوقع
- منع التراجع: الاختبارات تمنع الأخطاء القديمة من الظهور مرة أخرى
- تكامل CI/CD: الاختبار التلقائي ضروري للنشر المستمر
المعيار الصناعي: تحافظ شركات مثل Google و Facebook و Amazon على تغطية اختبار تزيد عن 80٪ لواجهاتها البرمجية. Laravel نفسه لديه أكثر من 6000 اختبار يضمن استقرار الإطار.
PHPUnit لواجهات Laravel البرمجية
تتضمن Laravel PHPUnit جاهزًا مع أدوات اختبار قوية مصممة خصيصًا لاختبار الواجهات البرمجية:
التثبيت والإعداد
# PHPUnit مضمن مع Laravel
# تشغيل الاختبارات
php artisan test
# أو استخدام PHPUnit مباشرة
./vendor/bin/phpunit
# تشغيل فئة اختبار محددة
php artisan test --filter ProductApiTest
# تشغيل الاختبارات مع التغطية
php artisan test --coverage
</div>
بنية الاختبار الأساسية
<?php
namespace Tests\Feature\Api;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use App\Models\User;
use App\Models\Product;
class ProductApiTest extends TestCase
{
use RefreshDatabase; // إعادة تعيين قاعدة البيانات بعد كل اختبار
/**
* اختبار جلب جميع المنتجات
*/
public function test_can_fetch_all_products()
{
// الترتيب: إنشاء بيانات الاختبار
Product::factory()->count(5)->create();
// الفعل: إجراء طلب API
$response = $this->getJson('/api/products');
// التأكيد: التحقق من الاستجابة
$response->assertStatus(200)
->assertJsonCount(5, 'data')
->assertJsonStructure([
'data' => [
'*' => [
'id',
'name',
'price',
'description',
'created_at',
'updated_at'
]
]
]);
}
/**
* اختبار جلب منتج واحد
*/
public function test_can_fetch_single_product()
{
$product = Product::factory()->create([
'name' => 'منتج تجريبي',
'price' => 99.99
]);
$response = $this->getJson("/api/products/{$product->id}");
$response->assertStatus(200)
->assertJson([
'data' => [
'id' => $product->id,
'name' => 'منتج تجريبي',
'price' => 99.99
]
]);
}
/**
* اختبار 404 لمنتج غير موجود
*/
public function test_returns_404_for_non_existent_product()
{
$response = $this->getJson('/api/products/99999');
$response->assertStatus(404)
->assertJson([
'message' => 'المنتج غير موجود'
]);
}
}
</div>
اختبار طلبات POST والتحقق
اختبار عمليات الإنشاء يضمن عمل قواعد التحقق بشكل صحيح وتخزين البيانات بشكل صحيح:
<?php
namespace Tests\Feature\Api;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use App\Models\User;
use App\Models\Category;
class ProductCreationTest extends TestCase
{
use RefreshDatabase;
/**
* اختبار إنشاء منتج ببيانات صالحة
*/
public function test_can_create_product_with_valid_data()
{
$user = User::factory()->create();
$category = Category::factory()->create();
$productData = [
'name' => 'منتج جديد',
'description' => 'منتج رائع',
'price' => 149.99,
'category_id' => $category->id,
'stock' => 100
];
$response = $this->actingAs($user)
->postJson('/api/products', $productData);
$response->assertStatus(201)
->assertJson([
'data' => [
'name' => 'منتج جديد',
'price' => 149.99
]
]);
// التحقق من وجود سجل قاعدة البيانات
$this->assertDatabaseHas('products', [
'name' => 'منتج جديد',
'price' => 149.99
]);
}
/**
* اختبار فشل التحقق ببيانات غير صالحة
*/
public function test_validation_fails_with_invalid_data()
{
$user = User::factory()->create();
$response = $this->actingAs($user)
->postJson('/api/products', [
'name' => '', // حقل مطلوب فارغ
'price' => -10, // سعر سلبي
]);
$response->assertStatus(422)
->assertJsonValidationErrors(['name', 'price', 'category_id']);
}
/**
* اختبار الوصول غير المصرح به
*/
public function test_requires_authentication()
{
$response = $this->postJson('/api/products', [
'name' => 'منتج'
]);
$response->assertStatus(401);
}
}
</div>
تأكيدات JSON
توفر Laravel أساليب تأكيد JSON قوية لاختبار استجابات الواجهات البرمجية:
<?php
// تأكيد تطابق JSON الدقيق
$response->assertExactJson([
'data' => [
'id' => 1,
'name' => 'منتج'
]
]);
// تأكيد بنية JSON
$response->assertJsonStructure([
'data' => [
'id',
'name',
'category' => [
'id',
'name'
]
]
]);
// تأكيد قيمة مسار JSON
$response->assertJsonPath('data.name', 'اسم المنتج');
$response->assertJsonPath('data.price', 99.99);
// تأكيد وجود جزء JSON
$response->assertJsonFragment([
'name' => 'اسم المنتج',
'price' => 99.99
]);
// تأكيد عدم وجود JSON
$response->assertJsonMissing([
'secret_field' => 'value'
]);
// تأكيد عدد JSON
$response->assertJsonCount(10, 'data');
// تأكيد أخطاء التحقق
$response->assertJsonValidationErrors(['name', 'email']);
</div>
الاختبار باستخدام المصانع
تنشئ مصانع النماذج بيانات اختبار واقعية بكفاءة:
<?php
namespace Database\Factories;
use App\Models\Product;
use App\Models\Category;
use Illuminate\Database\Eloquent\Factories\Factory;
class ProductFactory extends Factory
{
protected $model = Product::class;
public function definition()
{
return [
'name' => $this->faker->words(3, true),
'description' => $this->faker->paragraph(),
'price' => $this->faker->randomFloat(2, 10, 1000),
'category_id' => Category::factory(),
'stock' => $this->faker->numberBetween(0, 500),
'is_featured' => $this->faker->boolean(20),
];
}
/**
* حالة المصنع للمنتجات باهظة الثمن
*/
public function expensive()
{
return $this->state(function (array $attributes) {
return [
'price' => $this->faker->randomFloat(2, 500, 5000),
];
});
}
/**
* حالة المصنع للمنتجات نفدت من المخزون
*/
public function outOfStock()
{
return $this->state(function (array $attributes) {
return [
'stock' => 0,
];
});
}
}
</div>
استخدام المصانع في الاختبارات:
<?php
// إنشاء منتج واحد
$product = Product::factory()->create();
// إنشاء عدة منتجات
$products = Product::factory()->count(10)->create();
// إنشاء بسمات مخصصة
$product = Product::factory()->create([
'name' => 'اسم محدد',
'price' => 99.99
]);
// استخدام حالات المصنع
$expensiveProduct = Product::factory()->expensive()->create();
$featuredProducts = Product::factory()->featured()->count(5)->create();
$outOfStock = Product::factory()->outOfStock()->create();
</div>
استراتيجيات اختبار قاعدة البيانات
سمة RefreshDatabase
النهج الأكثر شيوعًا - يعيد تعيين قاعدة البيانات بعد كل اختبار:
<?php
namespace Tests\Feature\Api;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
class ProductApiTest extends TestCase
{
use RefreshDatabase;
// كل اختبار يحصل على قاعدة بيانات جديدة
public function test_example()
{
// قاعدة البيانات فارغة في البداية
Product::factory()->create();
// بعد الاختبار، يتم إعادة تعيين قاعدة البيانات
}
}
</div>
تأكيدات قاعدة البيانات
<?php
// تأكيد وجود سجل في قاعدة البيانات
$this->assertDatabaseHas('products', [
'name' => 'اسم المنتج',
'price' => 99.99
]);
// تأكيد عدم وجود سجل في قاعدة البيانات
$this->assertDatabaseMissing('products', [
'name' => 'منتج محذوف'
]);
// تأكيد عدد السجلات
$this->assertDatabaseCount('products', 10);
// تأكيد وجود النموذج
$this->assertModelExists($product);
</div>
اختبار المصادقة والترخيص
<?php
namespace Tests\Feature\Api;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use App\Models\User;
use App\Models\Product;
class AuthenticationTest extends TestCase
{
use RefreshDatabase;
/**
* اختبار أن الواجهة البرمجية تتطلب المصادقة
*/
public function test_unauthenticated_users_cannot_create_products()
{
$response = $this->postJson('/api/products', [
'name' => 'منتج'
]);
$response->assertStatus(401);
}
/**
* اختبار الوصول المصادق عليه باستخدام actingAs()
*/
public function test_authenticated_users_can_create_products()
{
$user = User::factory()->create();
$response = $this->actingAs($user)
->postJson('/api/products', [
'name' => 'منتج',
'price' => 50,
'category_id' => 1
]);
$response->assertStatus(201);
}
/**
* اختبار مصادقة رمز Bearer
*/
public function test_can_authenticate_with_bearer_token()
{
$user = User::factory()->create();
$token = $user->createToken('test-token')->plainTextToken;
$response = $this->withHeaders([
'Authorization' => 'Bearer ' . $token,
])->getJson('/api/user');
$response->assertStatus(200)
->assertJson([
'data' => [
'id' => $user->id,
'email' => $user->email
]
]);
}
}
</div>
اختبار الترقيم
<?php
public function test_products_are_paginated()
{
// إنشاء 30 منتج
Product::factory()->count(30)->create();
// طلب الصفحة الأولى
$response = $this->getJson('/api/products?page=1&per_page=10');
$response->assertStatus(200)
->assertJsonCount(10, 'data')
->assertJsonPath('meta.total', 30)
->assertJsonPath('meta.current_page', 1)
->assertJsonPath('meta.last_page', 3);
}
</div>
اختبار تحميل الملفات
<?php
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
public function test_can_upload_product_image()
{
Storage::fake('public');
$user = User::factory()->create();
$product = Product::factory()->create();
$file = UploadedFile::fake()->image('product.jpg', 600, 400);
$response = $this->actingAs($user)
->postJson("/api/products/{$product->id}/image", [
'image' => $file
]);
$response->assertStatus(200);
// تأكيد تخزين الملف
Storage::disk('public')->assertExists('products/' . $file->hashName());
}
</div>
تمرين عملي:
- إنشاء مجموعة اختبار كاملة لواجهة برمجية للمنتجات مع 10 اختبارات على الأقل
- اختبار جميع عمليات CRUD (الإنشاء، القراءة، التحديث، الحذف)
- تنفيذ اختبارات لقواعد التحقق على 5 حقول على الأقل
- اختبار المصادقة والترخيص باستخدام actingAs()
- إنشاء مصنع مع 3 حالات مختلفة (مميز، نفد من المخزون، باهظ الثمن)
- اختبار الترقيم مع صفحات متعددة والتحقق من البيانات الوصفية
- إضافة اختبارات لوظائف التصفية والبحث
- اختبار التحقق من تحميل الملف (النوع، الحجم، الأبعاد)
- تنفيذ اختبارات تحديد المعدل
- اختبار معالجة الأخطاء لاستجابات 404 و 422 و 500
- تحقيق تغطية كود 80٪ على الأقل لوحدة تحكم الواجهة البرمجية
أفضل ممارسات الاختبار
- اتبع نمط AAA: الترتيب (الإعداد)، الفعل (التنفيذ)، التأكيد (التحقق)
- تأكيد واحد لكل اختبار: اختبر سلوك واحد لكل طريقة اختبار
- استخدم أسماء وصفية: يجب أن تشرح أسماء الاختبار ما تختبره
- اختبر الحالات الحدية: اختبر الحدود، القيم الفارغة، المصفوفات الفارغة
- استخدم المصانع: قم بإنشاء بيانات اختبار واقعية باستخدام المصانع
- اختبر الحالات السلبية: اختبر شروط الخطأ وفشل التحقق
- حافظ على استقلالية الاختبارات: يجب ألا تعتمد الاختبارات على بعضها البعض
- اختبارات سريعة: استخدم DatabaseTransactions للسرعة عند الإمكان
- اختبر الواجهة البرمجية العامة: اختبر السلوك، وليس تفاصيل التنفيذ
- حافظ على التغطية: اهدف إلى تغطية 80٪ + على الكود الحرج
تكامل CI/CD: قم بتشغيل مجموعة الاختبار الخاصة بك تلقائيًا على كل commit باستخدام GitHub Actions أو GitLab CI أو ما شابه. قم بتكوين خط الأنابيب الخاص بك لحظر الدمج إذا فشلت الاختبارات، مما يضمن الحفاظ على معايير جودة الكود.
الخلاصة
يضمن اختبار الواجهات البرمجية الشامل باستخدام PHPUnit وأدوات اختبار Laravel أن نقاط النهاية الخاصة بك موثوقة وآمنة وقابلة للصيانة. من خلال الجمع بين اختبارات الوحدة واختبارات التكامل وتأكيدات JSON والمصانع واستراتيجيات اختبار قاعدة البيانات، تنشئ شبكة أمان تلتقط الأخطاء مبكرًا وتتيح إعادة الهيكلة الواثقة. تؤدي الواجهات البرمجية المختبرة جيدًا إلى تجربة مطور أفضل وحوادث إنتاج أقل وتطوير ميزات أسرع.