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

استراتيجية الاختبار ومراجعة الهندسة

15 دقيقة الدرس 35 من 35

استراتيجية الاختبار ومراجعة الهندسة

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

اختيار استراتيجية الاختبار الصحيحة

اعتبارات نوع المشروع

شركة ناشئة/MVP: - التركيز: ردود فعل سريعة، المسارات الحرجة فقط - التوزيع: 60% وحدة، 30% ميزات، 10% شامل - هدف التغطية: 60-70% - الأولوية: المصادقة، الدفع، الميزات الأساسية تطبيق المؤسسة: - التركيز: الموثوقية، قابلية الصيانة - التوزيع: 70% وحدة، 20% تكامل، 10% شامل - هدف التغطية: 85%+ - الأولوية: جميع منطق الأعمال، التكاملات خدمة API فقط: - التركيز: اختبار العقد، موثوقية API - التوزيع: 50% وحدة، 40% API/تكامل، 10% عقد - هدف التغطية: 80%+ - الأولوية: نقاط النهاية، التحقق من البيانات، معالجة الأخطاء قاعدة كود قديمة: - التركيز: التحسين التدريجي - الاستراتيجية: اختبار الميزات الجديدة، إضافة اختبارات عند إصلاح الأخطاء - هدف التغطية: زيادة تدريجية (ابدأ بـ 40%، استهدف 70%) - الأولوية: المناطق عالية المخاطر، الكود الذي يتغير بشكل متكرر

أنماط هندسة الاختبار

نمط المستودع لقابلية الاختبار

<?php // بدون مستودع (أصعب للاختبار) class OrderController { public function store(Request $request) { $order = Order::create($request->all()); // استخدام Eloquent المباشر return response()->json($order); } } // مع المستودع (أسهل للاختبار) class OrderController { public function __construct( private OrderRepository $orderRepository ) {} public function store(StoreOrderRequest $request) { $order = $this->orderRepository->create($request->validated()); return new OrderResource($order); } } // يصبح الاختبار بسيطًا class OrderControllerTest extends TestCase { /** @test */ public function stores_order() { $repository = Mockery::mock(OrderRepository::class); $repository->shouldReceive('create') ->once() ->andReturn(new Order()); $this->app->instance(OrderRepository::class, $repository); $response = $this->postJson('/orders', ['data' => 'test']); $response->assertCreated(); } }

نمط طبقة الخدمة

<?php // الخدمة تغلف منطق الأعمال المعقد class OrderProcessingService { public function __construct( private OrderRepository $orders, private PaymentGateway $payment, private EmailService $email, private InventoryService $inventory ) {} public function processOrder(Order $order, string $paymentToken): bool { DB::beginTransaction(); try { // شحن الدفع $charge = $this->payment->charge($order->total, $paymentToken); // تحديث الطلب $order->markAsPaid($charge->id); // تقليل المخزون foreach ($order->items as $item) { $this->inventory->decrease($item->product_id, $item->quantity); } // إرسال التأكيد $this->email->sendOrderConfirmation($order); DB::commit(); return true; } catch (Exception $e) { DB::rollBack(); throw new OrderProcessingException('Failed to process order', 0, $e); } } } // سهل الاختبار مع التقليد class OrderProcessingServiceTest extends TestCase { /** @test */ public function successfully_processes_paid_order() { $order = Order::factory()->make(); $payment = Mockery::mock(PaymentGateway::class); $payment->shouldReceive('charge')->andReturn((object)['id' => 'ch_123']); $inventory = Mockery::mock(InventoryService::class); $inventory->shouldReceive('decrease')->once(); $email = Mockery::mock(EmailService::class); $email->shouldReceive('sendOrderConfirmation')->once(); $service = new OrderProcessingService( new OrderRepository(), $payment, $email, $inventory ); $result = $service->processOrder($order, 'tok_visa'); $this->assertTrue($result); } }

استراتيجيات إدارة بيانات الاختبار

حالات المصنع للسيناريوهات المعقدة

<?php class OrderFactory extends Factory { public function definition(): array { return [ 'user_id' => User::factory(), 'status' => 'pending', 'total' => $this->faker->randomFloat(2, 10, 1000), ]; } public function pending(): self { return $this->state(['status' => 'pending']); } public function paid(): self { return $this->state([ 'status' => 'paid', 'paid_at' => now(), 'payment_id' => 'ch_' . Str::random(16) ]); } public function completed(): self { return $this->paid()->state([ 'status' => 'completed', 'completed_at' => now() ]); } public function cancelled(): self { return $this->state([ 'status' => 'cancelled', 'cancelled_at' => now() ]); } public function withItems(int $count = 3): self { return $this->has(OrderItem::factory()->count($count), 'items'); } } // الاستخدام في الاختبارات $pendingOrder = Order::factory()->pending()->create(); $paidOrder = Order::factory()->paid()->withItems(5)->create(); $completedOrder = Order::factory()->completed()->create();

البذور لاختبارات التكامل

<?php class TestDatabaseSeeder extends Seeder { public function run(): void { // بذور البيانات لاختبارات التكامل $this->call([ TestUserSeeder::class, TestProductSeeder::class, TestCategorySeeder::class, ]); } } class TestProductSeeder extends Seeder { public function run(): void { $categories = Category::all(); foreach ($categories as $category) { Product::factory() ->count(10) ->for($category) ->create(); } } } // الاستخدام في الاختبارات class ProductSearchTest extends TestCase { protected function setUp(): void { parent::setUp(); $this->seed(TestDatabaseSeeder::class); } /** @test */ public function can_search_products_by_category() { // الاختبار مع البيانات المبذورة } }

أنماط مضادة للاختبار يجب تجنبها

الأنماط المضادة الشائعة:

1. اختبار الطرق الخاصة:

الطرق الخاصة هي تفاصيل التنفيذ. اختبر الواجهة العامة بدلاً من ذلك.

// ❌ سيء $reflection = new ReflectionClass(Calculator::class); $method = $reflection->getMethod('calculateTax'); $method->setAccessible(true); $result = $method->invoke($calculator, 100); // ✅ جيد $result = $calculator->calculateTotal(100); // يختبر الطريقة العامة

2. ترابطات الاختبار:

يجب أن تكون الاختبارات مستقلة - لا يهم الترتيب.

// ❌ سيء /** @test */ public function test1_creates_user() { $this->user = User::create([...]); } /** @test */ public function test2_updates_user() // يعتمد على test1 { $this->user->update([...]); } // ✅ جيد /** @test */ public function can_update_user() { $user = User::factory()->create(); // إعداد مستقل $user->update([...]); }

3. الإفراط في التقليد:

لا تقلد ما لا تمتلكه. اختبر الكائنات الحقيقية عندما يكون ذلك ممكنًا.

// ❌ سيء: الإفراط في التقليد $product = Mockery::mock(Product::class); $product->shouldReceive('getName')->andReturn('Test'); $product->shouldReceive('getPrice')->andReturn(100); $product->shouldReceive('isInStock')->andReturn(true); // ✅ جيد: استخدم الكائنات الحقيقية $product = Product::factory()->create([ 'name' => 'Test', 'price' => 100, 'stock' => 10 ]);

4. النوم في الاختبارات:

لا تستخدم أبدًا sleep() - استخدم آليات انتظار مناسبة.

// ❌ سيء $this->post('/async-action'); sleep(5); // بطيء وغير موثوق $this->assertDatabaseHas(...); // ✅ جيد $this->post('/async-action'); $this->waitForDatabaseCount('jobs', 1);

بناء ثقافة الاختبار

ممارسات الفريق

تعزيز التطوير الموجه بالاختبار:

1. اجعل الاختبار سهلاً:

  • توفير مساعدات وأدوات الاختبار
  • توثيق أنماط الاختبار
  • إعداد بيئة اختبار سريعة
  • أتمتة تشغيل الاختبارات في CI/CD

2. إرشادات مراجعة الكود:

  • المطالبة باختبارات للميزات الجديدة
  • مراجعة جودة الاختبار، وليس التغطية فقط
  • فحص الأنماط المضادة للاختبار
  • التحقق من أن الاختبارات تختبر الشيء الصحيح فعلاً

3. مقاييس الاختبار لتتبعها:

  • اتجاهات تغطية الكود
  • وقت تنفيذ الاختبار
  • معدل الاختبار غير المستقر
  • معدل هروب الأخطاء (الأخطاء الموجودة في الإنتاج)

4. تدريب الفريق:

  • ورش عمل اختبار منتظمة
  • البرمجة الزوجية على الاختبارات
  • مشاركة انتصارات الاختبار والتعلم
  • الاحتفال بتحسين تغطية الاختبار

استراتيجية صيانة الاختبار

متى يتم تحديث الاختبارات

إعادة تصميم الكود → تحديث الاختبارات إذا تغير السلوك - إذا تغير التنفيذ فقط: يجب أن تنجح الاختبارات - إذا تغير السلوك: تحديث توقعات الاختبار إصلاح الخطأ → إضافة اختبار أولاً (نهج TDD) 1. اكتب اختبارًا فاشلاً يعيد إنتاج الخطأ 2. إصلاح الخطأ 3. الاختبار الآن ينجح 4. التزام كل من الإصلاح والاختبار إضافة ميزة → اكتب الاختبارات جنبًا إلى جنب مع الميزة - اختبارات الوحدة للفئات/الطرق الجديدة - اختبارات التكامل لتدفقات العمل - اختبارات شاملة لرحلات المستخدم إهمال الميزة → إزالة أو تخطي الاختبارات - حذف الاختبارات للكود المحذوف - تخطي الاختبارات للميزات المعطلة مؤقتًا

التعامل مع الاختبارات غير المستقرة

<?php // تحديد الاختبار غير المستقر /** @test */ public function sometimes_fails() // علامة حمراء: إخفاقات متقطعة { $response = $this->get('/api/random'); $this->assertEquals(200, $response->status()); // غير مستقر! } // الأسباب الشائعة والإصلاحات: // 1. شروط السباق // ❌ سيء $this->post('/process'); $this->assertTrue(ProcessingJob::wasDispatched()); // قد لا يتم إرساله بعد // ✅ جيد Queue::fake(); $this->post('/process'); Queue::assertPushed(ProcessingJob::class); // 2. الاختبارات المعتمدة على الوقت // ❌ سيء $user->created_at = now(); $this->assertTrue($user->isNew()); // يفشل إذا تم تشغيل الاختبار ببطء // ✅ جيد Carbon::setTestNow(now()); $user->created_at = now(); $this->assertTrue($user->isNew()); // 3. البيانات العشوائية // ❌ سيء $product = Product::inRandomOrder()->first(); $this->assertEquals('Expected Name', $product->name); // عشوائي! // ✅ جيد $product = Product::factory()->create(['name' => 'Expected Name']); $this->assertEquals('Expected Name', $product->name);

تقنيات الاختبار المتقدمة

اختبار العقد

<?php // التحقق من عقود API بين الخدمات class UserApiContractTest extends TestCase { /** @test */ public function user_api_response_matches_contract() { $response = $this->getJson('/api/users/1'); $response->assertOk() ->assertJsonStructure([ 'id', 'name', 'email', 'created_at', 'updated_at' ]) ->assertJsonCount(5); // عدد الحقول الدقيق // التحقق من أنواع الحقول $data = $response->json(); $this->assertIsInt($data['id']); $this->assertIsString($data['name']); $this->assertIsString($data['email']); } }

اختبار الطفرة

# تثبيت اختبار الطفرة composer require --dev infection/infection # تشغيل اختبارات الطفرة vendor/bin/infection # مثال: اختبار الطفرة يكشف اختبارًا ضعيفًا الكود الأصلي: if ($price > 100) { return $price * 0.9; // خصم 10% } المتحور (تغيير > إلى >=): if ($price >= 100) { return $price * 0.9; } إذا نجح الاختبار لا يزال → اختبار ضعيف! تحتاج إلى إضافة حالة اختبار لـ price === 100

قائمة التحقق من الاختبار

قائمة التحقق قبل النشر:
  • ✅ تنجح جميع الاختبارات محليًا وفي CI
  • ✅ التغطية تلبي معايير المشروع (80%+)
  • ✅ لا توجد اختبارات محذوفة أو معطلة
  • ✅ لا توجد اختبارات غير مستقرة في آخر 30 يومًا
  • ✅ اختبارات الأداء تنجح
  • ✅ اختبارات الأمان تنجح (لا حقن SQL، XSS، إلخ.)
  • ✅ اختبارات إمكانية الوصول تنجح (WCAG AA)
  • ✅ اختبارات الحمل تنجح لحركة المرور المتوقعة
  • ✅ تم اختبار خطة التراجع

الخلاصة: فلسفة الاختبار

المبادئ الأساسية:

1. الاختبارات هي التوثيق - إنها تشرح كيف يجب أن يعمل الكود الخاص بك

2. الملاحظات السريعة أمر بالغ الأهمية - الاختبارات البطيئة لا يتم تشغيلها

3. اختبار السلوك، وليس التنفيذ - يجب أن تنجو الاختبارات من إعادة التصميم

4. التغطية مقياس، وليست هدفًا - تغطية 100% ≠ اختبارات جيدة

5. اكتب الاختبارات للثقة - انشر بدون خوف

6. الاختبارات هي كود من الدرجة الأولى - حافظ عليها مثل كود الإنتاج

7. TDD عندما يساعد - ليس عقائديًا، ولكنه غالبًا ما يكون مفيدًا

التحدي النهائي:

راجع مشروعك الحالي و:

  1. حدد المسارات الحرجة غير المختبرة
  2. اكتب الاختبارات المفقودة للمناطق عالية المخاطر
  3. أعد تصميم مجموعة اختبار واحدة لاتباع أفضل الممارسات
  4. قم بإعداد CI/CD إذا لم يتم تكوينه بالفعل
  5. وثق استراتيجية الاختبار الخاصة بك لفريقك
  6. قس التغطية الحالية وحدد أهداف التحسين
  7. جدولة جلسات صيانة الاختبار المنتظمة

استنتاج الدورة

لقد أكملت الآن رحلة شاملة عبر الاختبار في PHP و Laravel:

  • الأساسيات: PHPUnit، أنواع الاختبار، التأكيدات
  • اختبار Laravel: اختبارات HTTP، قاعدة البيانات، التقليد، قوائم الانتظار
  • الموضوعات المتقدمة: اختبار API، Dusk، WebSockets، إمكانية الوصول
  • أفضل الممارسات: تنظيم الاختبار، CI/CD، التغطية، التوثيق
  • العالم الحقيقي: بناء مجموعة اختبار كاملة لمنصة التجارة الإلكترونية

الخطوات التالية:

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

اختبار سعيد! 🧪✅

اكتمل الدرس!

تهانينا! لقد أكملت جميع الدروس في هذا البرنامج التعليمي.