اذهب إلى المحتوى
  • 0

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

Mahmoud Alrashidi

السؤال

Recommended Posts

  • 0

إطار العمل لارافيل مُجهز بشكل جيد للقيام بالإختبارات بشتى أنواعها يُمكنك إستخدام قاعدة بيانات ثانية للقيام بالإختبارات عليها لكن أي مشروع تقوم بإنشائه ستجده مُجهز مُسبقاً للقيام بالإختبارات على قاعدة بيانات من نوع sqlite في الذاكرة أي لست بحاجة لإنشاء قاعدة بيانات بالأساس فكما تعلم أن التعامل مع الذاكرة يكون أسرع من التعامل مع ملف في القرص الصلب لذلك تحتاج فقط للذهاب إلى ملف  phpunit.xml حيث ستجد السطرين التاليين:

<!-- <server name="DB_CONNECTION" value="sqlite"/> -->
<!-- <server name="DB_DATABASE" value=":memory:"/> -->

مٌعلقين فقط قم بإزالة التعليق: 

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

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

class BookTest extends TestCase
{

    /** @test */
    public function a_book_can_be_added_to_the_library()
    {
		
    }
}

ماهي خطوات إضافة كتاب؟ أليست كالتالي إرسال بيانات الكتاب إلى مسار مُعين ثم التأكد من أن الكتاب أضيف لقاعدة البيانات إذا لنكتب هذه الخطوات داخل التابع الذي أنشأناه:

<?php

$this->post('/books', [
  'title' => 'Book one',
  'author' => 'Kamel',
]);

$this->assertCount(1, Book::all());

إذا قمنا بذلك ثم نفذنا أمر phpunit سيفشل الإختبار بالطبع لكنه سيفشل في الخطوة الثانية و نحن ننتظر منه أن يفشل في الخطوة الأولى، لماذا؟ لان لارافيل إفتراضياً تقوم بمُعالجة الإستثناءات التي تُرمى لذلك يجب أن نُخبرها بأن لا تُعالج الإستثناء و بأن تقوم بعرضه و لحسن الحظ هي توفر دالة للقيام بذلك إسمها withoutExceptionHandling و دالة أخرى إسمها withExceptionHandling و هي ما يتم تنفيذه لذلك لابد من تعديلها حتى نرى فشل الإختبار:

class BookTest extends TestCase
{

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

        $this->post('/books', [
            'title' => 'Book one',
            'author' => 'Kamel',
        ]);

        $this->assertCount(1, Book::all());
    }
}

الآن لو قمنا بتنفيذ أمر phpunit  سيفشل و سيُعطينا السبب:

NotFoundHttpException: POST http://localhost/books

هذا الإستثناء يعني أن المسار الذي نُحاول الوصول له غير موجود و بالتالي نفتح ملف web.php ثم نضيف المسار:

Route::post('/books', [BookController::class, 'store']);

بعد أن أنشأنا المسار نُعيد تجربة الإختبار و في هذه المرة سيظهر الخطأ التالي:

BindingResolutionException: Target class [BookController] does not exist.

ما يعني أن المُتحكم الذي أنشأناه غير موجود.

فلنقم بإنشائه:

php artisan make:controller BookController

ثم نقوم بإستدعائه داخل ملف web.php:

<?php

use App\Http\Controllers\BookController;

بعد ذلك نُعيد تجربة الإختبار عن طريق الأمر phpunit و في هذه الحالة سيفشل الإختبار بالسبب التالي:

BadMethodCallException: Method App\Http\Controllers\BookController::store does not exist.

يعني أن التابع store غير موجود في المُتحكم BookController دعنا نضيفه:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class BookController extends Controller
{
    public function store() {
        
    }
}

مرة أخرى نُجرب الأمر phpunit و سيفشل الإختبار لكن في هذه المرة سيفشل في الخطوة الثانية و سيُعطي السبب التالي:

Class 'Tests\Feature\Book' not found

أي أن الصنف Book غير موجود دعنا نقوم بإضافة النموذج و ملف التهجير الخاص به و نستدعيه في كلاس الإختبار فوق:

php artisan make:model Book -m

وفي الكلاس BookTest نقوم بإستدعائه:

use App\Models\Book;

مرة أخرى نُجرب الأمر phpunit و بطبيعة الحال سيفشل أيضا و سيُعطي السبب التالي:

QueryException: SQLSTATE[HY000]: General error: 1 no such table: books (SQL: select * from "books")

هذا معناه أنه يُحاول جلب كل الكتب لكن نحن لغاية الآن لم نقم بتهجير قاعدة البيانات و نرجع لأول ما أخبرتك به أن إستعمال قاعدة بيانات ثانية من أجل الإختبارات لذلك نقوم بإلغاء التعليق من السطرين في ملف phpunit.xml، ثم نضيف ال trait الذي إسمه RefreshDatabase داخل صنف الإختبار ستجد أنه قد تم إستدعاؤه لكنه غير مستخدم دعنا نستخدمه:

use Illuminate\Foundation\Testing\RefreshDatabase;

class BookTest extends TestCase
{
    use RefreshDatabase; 

}

و هذا ال trait الهدف منه هو تنفيذ أمر التهجير عند تنفيذ الإختبار و عند إنتهاء الإختبار يقوم بعمل reset.

بعد ذلك نقوم مرة أخرى بتنفيذ الأمر phpunit و بطبيعة الحال سيفشل أيضا و سيُعطي السبب التالي:

Failed asserting that actual size 0 matches expected size 1.

هذا الخطأ يعني أنه لم يتم إضافة الكتاب فهو جلب كل الكتب لكن وجد أن عددها 0 لكن نحن توقعنا 1 و هذا لأن الدالة store لا تقوم بشيء لغاية الآن، دعنا نضيف أكواد إضافة كتاب:

use App\Models\Book;

class BookController extends Controller
{
    public function store() {
        Book::create([
            'title' => request('title'),
            'author' => request('author')
        ]);
    }
}

بعد ذلك نقوم مرة أخرى بتنفيذ الأمر phpunit و بطبيعة الحال سيفشل لكن هذه المرة سيُعطي الخطأ التالي:

MassAssignmentException: Add [title] to fillable property to allow mass assignment

و هذا لأن لارافيل تُحاول حمايتك يُمكننا في هذه الحالة إخبار لارافيل أننا سنقوم بحماية أنفسنا و لا داعي أن تتدخل في هذا الأمر بعمل set للخاصية guarded في النموذج Book و جعلها  فارغة:

class Book extends Model
{
    use HasFactory;
    protected $guarded = [];
}

بعد ذلك نقوم مرة أخرى بتنفيذ الأمر phpunit و بطبيعة الحال سيفشل أيضاً بسبب أن ملف التهجير الخاص بالكتب لا يوجد به الأعمدة title و author:

QueryException: SQLSTATE[HY000]: General error: 1 table books has no column named title

نقوم بإضافتها في ملف التهجير:

$table->string('title');
$table->string('author');

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

و هكذا تقوم بإتباع نفس الآلية في بقية الإختبارات، نحن لم نختبر ال validation و لم نختبر إعادة توجيه المُستخدم مثلاً ، يُمكنك إختبار الخصائص بنفس الطريقة

رابط هذا التعليق
شارك على الشبكات الإجتماعية

انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أجب على هذا السؤال...

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.

  • إعلانات

  • تابعنا على



×
×
  • أضف...