أشارك ما أعرفه عن البرمجة، وتجارب أخرى.

إنشاء عمليات CRUD بالبرمجة الموجهة للإختبار

تم نشرها بتاريخ 2020-05-04 في شروح ، برمجيات

متابعةً على للمقالة السابقة التي قمت بالتوضيح بها عن طريقتي التي أتبعها لكتابة اختبارات Unit للعلاقات بين Models سأقوم بهذه المقالة و المقالات التي تتبعها توضيح الطرق التي أتبعها عند اختبار عمليات CRUD وماهي الاحتمالات التي أقوم بتغطيتها لكل عملية منها.


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

  • يمكن للمستخدم إنشاء مهمة جديدة له 
  • عرض قائمة بمهامه فقط
  • التعديل على مهامه فقط
  • حذف مهامه فقط


سأقوم بهذه المقالة بالشرح على أول عملية وهي اﻹضافة وما يترتب عليها من إحتمالات أخرى يجب اختبارها سنقوم بالتطرق إليها لاحقاً.


سأبدأ بمشروع لارافل جديد أولا نبدأ بكتابة الاختبار إضافة مهمة جديدة لمستخدم، وبهذا سنحتاج إنشاء كلاس اختبار خاص باختبارات feature المهام (Task) الذي نقوم بإنشائه عن طريق الامر التالي، لنجده تحت المجلد Tests/Feature


 Php artisan make:test TaskTest


ونقوم أولا بكتابة دالة اختبار تقوم بتأكد من أنه يمكن للمستخدم قد قام بتسجيل دخوله إنشاء مهمة جديدة ليتم تلقائيا تعيين هذه المهمة له ومن قائمة مهامه، وستكون كالتالي:


class TaskTest extends TestCase
{
    /**
     *@test
     */
    public function authenticated_users_can_create_new_task_automatically_assigned_to_them()
    {
        $user = factory('App\User')->create();

        $this->actingAs($user);

        $this->post('/tasks', [
            'title'       => $title       = 'write a blog post',
            'description' => $description = 'write a post about CRUD operations using TDD'
        ]);

        $this->assertDatabaseHas('tasks', [
            'title'       => $title,
            'description' => $description,
            'user_id'     => auth()->id()
        ]);
    }
}



ما الذي نعنيه بهذه الاكواد المكتوبة؟  

هي فقط ترجمة للشرح الذي أردنا اختباره،  في السطر الأول من الدالة قمنا  بإنشاء مستخدم جديد عن طريق Laravel Factories ومن ثم قمنا بتسجيل دخول هذا المستخدم، ومحاكاةً للواقع افترضنا أنه قام بإضافة مهمة جديدة عن طريق POST Request وقام بإدخال كلا من عنوان المهمة ووصف عنها، لنقوم بالنهاية من التأكد من أن جدول المهام tasks بقاعدة البيانات يحتوي على صف به العنوان والوصف الذي قمنا بإدخاله وأيضا تأكدنا من رقم المستخدم المسندة إليه هذه المهمة هو نفسه رقم المستخدم المسجل دخوله حاليا. لنقوم بتنفيذ الاختبار عن طريق تنفيذ الأمر


./vendor/bin/phpunit  --filter authenticated_users_can_create_new_task_automatically_assigned_to_them



حسب ما موضح فإن الاختبار قد بفشل بسبب عدم قدرته على الاتصال بقاعدة البيانات، ولحل هذا الخطأ سنقوم باستخدام قاعدة بيانات sqlite داخل الذاكرة وبدون تكوين ملف قاعدة البيانات وذلك لتسهيل وتسريع عملية الاختبار، ونقوم بهذا عن طريق إضافة هذه الاعدادات بملف phpunit.xml بداخل عنصر <php></php>


  <env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>


واﻷن نقوم بإعادة تنفيذ الاختبار


هذه المرة يوضح الخطأ أنه لا يوجد جدول المستخدمين users وذلك لأننا قمنا بتكوين مستخدم جديد ولكنه لا يستطيع إيجاد الجدول لإضافة هذا المستخدم عليه، ولتكوين الجداول عن طريق migrations يوجد لدى لارافل Trait خاصة بتنفيذ اﻷمر php artisan migrate:fresh كل مرة يتم فيها بتنفيذ هذا اﻹختبار، ليتم إعادة تكوين قاعدة البيانات ومسح البيانات السابقة، نقوم باستخدامها كالتالي:


class TaskTest extends TestCase
{
    use RefreshDatabase;


ونعيد تنفيذ الاختبار


هذه المرة نلاحظ أنه يقف عند السطر الذي نقوم له بالتأكد من أن جدول المهام tasks يحتوي على المهمة التي كونها، ونلاحظ أن الخطأ أنه لايجد جدول بهذا الاسم لأننا لم نقم بتكوين واحد بعد، إذا فلنقم بتكوين هذا الجدول عن طريق الامر التالي:

php artisan make:migration create_tasks_table --create=tasks


ولنقم بإضافة الحقول التي نريدها على هذا الجدول


public function up()
    {
       Schema::create('tasks', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('title');
            $table->text('description');
            $table->unsignedBigInteger('user_id');
            $table->timestamps();
        });

    }



والأن نقوم بإعادة تنفيذ الاختبار من جديد لتصحيح الخطأ التالي


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


ولكن هل من الممكن أن يخبرنا الاختبار عن أخطاء مفصلة كما يحدث الآن كأن يقول لنا أنه لا يوجد route الذي نذهب إليه، أو أن يقول أن هذه الدالة غير موجودة؟

بالتأكيد يمكننا ذلك، يقوم Laravel بشكل إفتراضي بدوال الاختبار Feature بإرجاع Exceptions ذات رسائل أخطاء مفهومة ومختصرة، ولكننا في هذه الحالة نريد تصحيح الأخطاء ومعرفة مصدر الأخطاء ولفعل هذا يمكننا إلغاء تفعيل Exceptions Handling، لفهمها بتفصيل أكثر يمكنك الاطلاع على الحلقة Toggle Exception Handling within Tests 



/**
     *@test
     */
    public function authenticated_users_can_create_new_task_automatically_assigned_to_them()
    {
        $this->withoutExceptionHandling();

        $user = factory('App\User')->create();

        $this->actingAs($user);

        $this->post('/tasks', [
            'title'       => $title       = 'write a blog post',
            'description' => $description = 'write a post about CRUD operations using TDD'
        ]);

        $this->assertDatabaseHas('tasks', [
            'title'       => $title,
            'description' => $description,
            'user_id'     => auth()->id()
        ]);
    }



وعند إعادة تنفيذ الإختبار


نلاحظ اﻵن أن الرسالة تغيرت حيث تبين لنا أنه ليس لدينا Route بهذا المسار، إذا نقوم بإضافته ونقوم بإعادة الاختبار.

Route::post('/tasks', 'TaskController@store');



الخطأ اﻵن يوضح أنه لا يوجد controller بهذا الاسم، لنقم بإضافته

php artisan make:controller TaskController



الآن يجب علينا إضافة دالة store وكتابة أكواد اﻹضافة


class TaskController extends Controller
{
    public function store()
    {
        request()->validate([
            'title'       => 'required|string',
            'description' => 'required'
        ]);

        Task::create([
            'title'       => request('title'),
            'description' => request('description'),
            'user_id'     => auth()->id()
        ]);


        return redirect()->back();
    }
}



نلاحظ أن هذه المرة الخطأ يوضح أنه لا يوجد Model باسم Task، وذلك لأنني لم أقم بإضافة واحد، إذا سأقوم بإضافة واحد عن طريق الأمر :

php artisan make:model Task


ثم نقوم باستدعاء المسار الصحيح لTask Model بملف اﻹختبار


use App\Task



أما الآن فإن الخطأ يوضح أنه يجب أن نضيف title, description لمصفوفة fillable حتى يمكننا إضافة قيمهم بإستخدام Model.



class Task extends Model
{
    protected $fillable = [
        'title',
        'description',
        'user_id'
    ];

}


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


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


خولة الشح

تمت كتابتها بواسطة خولة الشح