0Pricing
PHP Academy · درس

سير عمل التطوير المدفوع بالاختبارات

وجّه التصميم بدورة الأحمر-الأخضر-إعادة الهيكلة

سير عمل التطوير المدفوع بالاختبارات درس مجاني في PHP Academy على CoddyKit. هذا هو الدرس 1 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في PHP Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة PHP Academy 4 دروس في المجموع.

TDD أداة للتصميم

غالبًا ما يُقدَّم التطوير القائم على الاختبارات على أنه أسلوب للاختبار، لكن قيمته الحقيقية تكمن في الضغط التصميمي. فعندما تكتبون الاختبار أولًا، تُجبرون على تحديد الواجهة العامة للكائن، وتبعياته، وعقده قبل كتابة أي تنفيذ. الاختبارات نتيجة جانبية؛ أما المنتج الحقيقي فهو تصميم أفضل.

أحمر، أخضر، إعادة الهيكلة

تتكوّن الدورة من ثلاث مراحل صارمة:

  • أحمر — اكتبوا اختبارًا فاشلًا للسلوك الصغير التالي. يجب أن يفشل للسبب الصحيح.
  • أخضر — اكتبوا الحد الأدنى من الكود اللازم للنجاح، حتى إن كان قبيحًا.
  • إعادة الهيكلة — نظّفوا التنفيذ والاختبارات مع الحفاظ على الحالة الخضراء.

الانضباط مهم: لا تتجاوزوا مرحلة الأحمر مطلقًا، فقد تكونون تختبرون لا شيء، ولا تبالغوا في البناء أثناء المرحلة الخضراء.

الأحمر: اكتبوا الاختبار الفاشل أولًا

سننشئ PriceCalculator لتطبيق خصم بنسبة مئوية. ابدأوا باختبار سلوك غير موجود بعد. يجب أن يفشل تشغيله لأن الصنف غير معرّف — وهذا فشل أحمر صحيح.

<?php
use PHPUnit\Framework\TestCase;

final class PriceCalculatorTest extends TestCase
{
    public function test_applies_a_percentage_discount(): void
    {
        $calc = new PriceCalculator();
        // 100 with 10% off -> 90.0
        self::assertSame(90.0, $calc->withDiscount(100.0, 10));
    }
}

الأخضر: الحد الأدنى من الكود للنجاح

اكتبوا الآن أصغر تنفيذ يحوّل المؤشر إلى اللون الأخضر. قاوموا إضافة قواعد التقريب أو التحقّق أو التعامل مع العملات — فلا يوجد اختبار فاشل يطالب بها بعد.

<?php
final class PriceCalculator
{
    public function withDiscount(float $amount, float $percent): float
    {
        return $amount - ($amount * $percent / 100);
    }
}

$calc = new PriceCalculator();
var_dump($calc->withDiscount(100.0, 10)); // float(90)

استخدموا التثليث لدفع التعميم

يمكن تلبية اختبار واحد عبر ترميز قيمة ثابتة. أما التثليث — أي إضافة مثال ثانٍ مختلف — فيجبر التنفيذ على التعميم. أضيفوا حالة لا يستطيع الحل البسيط تزييفها، لدفع الصيغة الحقيقية إلى الظهور.

<?php
use PHPUnit\Framework\TestCase;

final class PriceCalculatorTest extends TestCase
{
    public function test_zero_percent_is_unchanged(): void
    {
        self::assertSame(50.0, (new PriceCalculator())->withDiscount(50.0, 0));
    }

    public function test_full_discount_is_free(): void
    {
        self::assertSame(0.0, (new PriceCalculator())->withDiscount(50.0, 100));
    }
}

اجعلوا الحالات الحدّية مواصفات

في TDD، كل متطلب جديد يعني اختبارًا فاشلًا جديدًا. افترضوا أن الخصومات السالبة غير صالحة. اكتبوا أولًا اختبارًا يتوقع استثناءً قبل أي شيء؛ سيفشل لأن لا شيء يرمي استثناءً بعد. يوثّق الاختبار العقد.

<?php
use PHPUnit\Framework\TestCase;

final class PriceCalculatorValidationTest extends TestCase
{
    public function test_rejects_negative_discount(): void
    {
        $this->expectException(\InvalidArgumentException::class);
        (new PriceCalculator())->withDiscount(100.0, -5);
    }
}

عودوا إلى الأخضر، ثم أعيدوا الهيكلة

أضيفوا الحماية لتمرير الاختبار الجديد، ثم أعيدوا الهيكلة بثقة — فالاختبارات الحالية تلتقط التراجعات. لاحظوا أننا أضفنا التقريب أيضًا مرة واحدة فقط بعد أن برّره اختبار أو متطلب معروف.

<?php
final class PriceCalculator
{
    public function withDiscount(float $amount, float $percent): float
    {
        if ($percent < 0 || $percent > 100) {
            throw new \InvalidArgumentException('percent must be 0..100');
        }
        return round($amount - ($amount * $percent / 100), 2);
    }
}

var_dump((new PriceCalculator())->withDiscount(19.99, 15)); // float(16.99)

موفّرو البيانات يختصرون الأمثلة

بعد استقرار سلوك ما، ادمجوا أزواج الأمثلة العديدة في اختبار واحد مَعْلَمي باستخدام موفّر بيانات. يحافظ ذلك على سرعة دورة الأحمر/الأخضر ويجعل جدول القصد سهل القراءة.

<?php
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\DataProvider;

final class DiscountTableTest extends TestCase
{
    #[DataProvider('cases')]
    public function test_discounts(float $amount, float $pct, float $expected): void
    {
        self::assertSame($expected, (new PriceCalculator())->withDiscount($amount, $pct));
    }

    public static function cases(): array
    {
        return [
            'no discount'   => [100.0, 0, 100.0],
            'ten percent'   => [100.0, 10, 90.0],
            'free'          => [100.0, 100, 0.0],
        ];
    }
}

حافظوا على سرعة الدورة

لا ينجح TDD إلا إذا استغرقت حلقة التغذية الراجعة ثواني لا دقائق. ومن الوسائل العملية:

  • شغّلوا ملفًا واحدًا أو استخدموا مرشّحًا: phpunit --filter test_full_discount_is_free.
  • استخدموا --testdox لقراءة الاختبارات كمواصفة للسلوك.
  • أبقوا اختبارات الوحدة خالية من عمليات الإدخال والإخراج — لا قاعدة بيانات أو شبكة أو نظام ملفات في الحلقة الداخلية.
vendor/bin/phpunit --testdox --filter PriceCalculator

أعيدوا هيكلة الاختبارات أيضًا

تشمل مرحلة إعادة الهيكلة مجموعة الاختبارات، لا كود الإنتاج وحده. أزيلوا التكرار باستخدام الدوال المساعدة وsetUp()، وسمّوا الاختبارات بحسب السلوك، واحذفوا الاختبارات التي لم تعد تؤكد شيئًا ذا معنى. الاختبارات كود ستصونونه إلى الأبد — فتعاملوا معها بالعناية نفسها.

<?php
use PHPUnit\Framework\TestCase;

final class PriceCalculatorTest extends TestCase
{
    private PriceCalculator $calc;

    protected function setUp(): void
    {
        $this->calc = new PriceCalculator(); // shared setup, no duplication
    }

    public function test_ten_percent(): void
    {
        self::assertSame(90.0, $this->calc->withDiscount(100.0, 10));
    }
}

يشكّل TDD التبعيات

لأنكم تكتبون الاختبار أولًا، تصبح التبعيات المربكة واضحة فورًا. إذا كان إنشاء كائن داخل اختبار صعبًا، فهذه ملاحظات تصميمية: احقنوا المتعاونين بدلًا من إنشاء كائناتهم باستخدام new داخل الصنف. يصبح حاسب الخصم أدناه قابلًا للاختبار بقبول سياسة التقريب، بدلًا من ترميزها، فقد دفع TDD هذا الفاصل إلى الوجود.

<?php
interface RoundingPolicy { public function round(float $v): float; }

final class PriceCalculator
{
    public function __construct(private RoundingPolicy $rounding) {}

    public function withDiscount(float $amount, float $percent): float
    {
        return $this->rounding->round($amount - ($amount * $percent / 100));
    }
}
// Tests inject a deterministic RoundingPolicy; no hidden global behavior.

تحقّق سريع

ما الهدف من مرحلة الأحمر؟

مراجعة

لقد تدرّبتم على TDD بوصفه منهجًا للتصميم:

  • الأحمر-الأخضر-إعادة الهيكلة: اختبار فاشل، ثم كود بالحد الأدنى، ثم تنظيف مع بقاء الحالة خضراء.
  • يفرض التثليث التعميم؛ وتصل المتطلبات الجديدة على شكل اختبارات فاشلة جديدة.
  • يختصر موفّرو البيانات الحالات المستقرة؛ ويحافظ --filter/--testdox على سرعة الدورة.
  • أعيدوا هيكلة اختباراتكم أيضًا — فهي كود طويل العمر.

التالي: البدائل المرنة للاختبار باستخدام Mockery.

الأسئلة الشائعة

هل درس «سير عمل التطوير المدفوع بالاختبارات» مجاني؟

نعم — نص درس «سير عمل التطوير المدفوع بالاختبارات» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة PHP Academy، انتقل إلى CoddyKit PRO. تتضمن دورة PHP Academy 4 دروس في المجموع.

ماذا ستتعلم في «سير عمل التطوير المدفوع بالاختبارات»؟

وجّه التصميم بدورة الأحمر-الأخضر-إعادة الهيكلة تتمرن على PHP Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ PHP Academy؟

لا تُشترط خبرة سابقة. PHP Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 1 من أصل 4.

كم من الوقت يستغرق درس «سير عمل التطوير المدفوع بالاختبارات»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس PHP Academy هذا؟

نعم. كل درس في PHP Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. سير عمل التطوير المدفوع بالاختبارات
  2. المحاكاة والبدائل باستخدام Mockery
  3. اختبار التكامل والاختبار الوظيفي
  4. اختبار الطفرات باستخدام Infection
← العودة إلى PHP Academy