اكتشف إطار PHP Laravel


الدرس: اختبارات الوحدة


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

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

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

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

اختبار الإشراف


PHPUnit
يستخدم Laravel PHPUnit لإجراء الاختبارات. إنه إطار تم إنشاؤه بواسطة سيباستيان بيرجمان والذي يعمل من خلال التأكيدات.
يتم تثبيت هذا الإطار كتبعية Laravel في وضع التطوير:

"require-dev": {
    "fzaninotto/faker": "~1.4",
    "mockery/mockery": "0.9.*",
    "phpunit/phpunit": "~4.0",
    "symfony/css-selector": "2.8.*|3.0.*",
    "symfony/dom-crawler": "2.8.*|3.0.*"
},
ولكن يمكنك أيضًا استخدام الملف phar  الذي يمكنك العثور عليه في هذه الصفحة   ووضعه في جذر التطبيق الخاص بك وأنت مستعد للاختبار!
الإصدار 5 من PHPUnit يتطلب PHP 5.6
يمكنك التحقق من أنه يعمل عن طريق إدخال هذا الأمر:

php phpunit.phar –h
يمنحك هذا قائمة بجميع الأوامر المتاحة.
إذا كنت تستخدم الإصدار المثبت مع Laravel فإنه يعطي:

php vendor\phpunit\phpunit\phpunit –h
أنصحك أن تجعل اسم مستعار( alias)   .
في كل الأمثلة في هذا الفصل سأستخدم الملف phar .
الإشراف على Laravel
إذا نظرت إلى ملفات Laravel ستجد ملفًا مخصصًا للاختبارات:
framework Laravel MVC
ملفات الاختبار
لديك بالفعل ملفين. هنا هو TestCase.php  :

<?php

class TestCase extends Illuminate\Foundation\Testing\TestCase
{
    /**
     * The base URL to use while testing the application.
     *
     * @var  string
     */
    protected $baseUrl = 'http://localhost';

    /**
     * Creates the application.
     *
     * @return  \Illuminate\Foundation\Application
     */
    public function createApplication()
    {
        $app = require __DIR__.'/../bootstrap/app.php';

        $app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();

        return $app;
    }
}
هذه الفئة مسؤولة عن إنشاء تطبيق للاختبار في بيئة معينة (والتي تسمح بإعداد تكوين مناسب للاختبار).
تمدد الفئة Illuminate\Foundation\Testing\TestCase و  التي بدورها تمدد الفئة PHPUnit_Framework_TestCase عن طريق إضافة بعض الوظائف العملية للغاية ، كما سنرى.
سيتعين على جميع فئات الاختبار التي تنشئها تمديد هذه الفئة TestCase . يوجد مثال للاختبار الموجود بالفعل ExampleTest.php  :

<?php

use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class ExampleTest extends TestCase
{
    /**
     * A basic functional test example.
     *
     * @return  void
     */
    public function testBasicExample()
    {
        $this->visit('/')
             ->see('Laravel 5');
    }
}
دون إدخال الكود في الوقت الحالي ، اعلم أننا نرسل طلبًا للطريق الأساسي وننتظر ردًا. لبدء هذا الاختبار ، يكون الأمر بسيطًا للغاية ، أدخل هذا الأمر:

php phpunit.phar
PHPUnit 4.8.21 by Sebastian Bergmann and contributors.

.

Time: 3290ms, Memory: 21.50Mb

OK (1 test, 2 assertions)
نرى أنه تم إجراء اختبار وتأكيدين وأن كل شيء سار بشكل جيد.
بيئة الاختبار
قلت لك إن الاختبارات تجرى في بيئة معينة ، وهي عملية للغاية. أين هذه الاعدادات؟ انظر إلى الملف phpunit.xml  :

<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="bootstrap/autoload.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false">
    <testsuites>
        <testsuite name="Application Test Suite">
            <directory suffix="Test.php">./tests</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist processUncoveredFilesFromWhitelist="true">
            <directory suffix=".php">./app</directory>
            <exclude>
                <file>./app/Http/routes.php</file>
            </exclude>
        </whitelist>
    </filter>
    <php>
        <env name="APP_ENV" value="testing"></env>
        <env name="CACHE_DRIVER" value="array"></env>
        <env name="SESSION_DRIVER" value="array"></env>
        <env name="QUEUE_DRIVER" value="sync"></env>
    </php>
</phpunit>
هناك بالفعل 4 متغيرات البيئة:
  • APP_ENV : هنا نقول أننا في وضع الاختبار ،
  • CACHE_DRIVER : في وضع "array" مما يعني أننا لن نقوم بتخزين أي شيء مؤقتًا خلال الاختبارات (بشكل افتراضي لدينا file ) ،
  • SESSION_DRIVER : في وضع "array" مما يعني أننا لن نستمر في الجلسة (بشكل افتراضي لدينا file) ،
  • QUEUE_DRIVER : أنا لا أتحدث عن المهام في هذه الدورة ، لذلك لن نأخذ هذا المتغير في الاعتبار.
من الواضح أننا يمكن أن نضيف المتغيرات التي نحتاجها. على سبيل المثال ، إذا كنت لا أريد بعد الاختبارات ، MySql ولكن sqlite . هناك متغير في config/database.php :

<?php
'default' => env('DB_CONNECTION', 'mysql'),
لذلك يجب إدخالها في .env :

DB_CONNECTION=mysql
لذلك phpunit.xml أستطيع أن أكتب:

    <env name="DB_CONNECTION" value="sqlite"/>
الآن للاختبارات سأستخدمها sqlite .

بناء اختبار


المراحل الثلاث للاختبار
لإنشاء اختبار ، ننتقل عمومًا على ثلاث مراحل:
  1. نُتهيئ البيانات ،
  2. نعمل على هذه البيانات ،
  3. نتحقق من أن النتيجة تتماشى مع توقعاتنا.
لأن كل هذا هو مجرد ملخص دعونا نأخذ مثالا على ذلك. استبدل كود الطريقة بهذا الكود testBasicExample :

<?php
public function testBasicExample()
{
    $data = [10, 20, 30];
    $result = array_sum($data);
    $this->assertEquals(60, $result);
}
نجد لدينا ثلاث مراحل. نهيئ البيانات:

<?php
$data = [10, 20, 30];
نعمل على هذه البيانات:

<?php
$result = array_sum($data);
اختبار النتيجة:

<?php
$this->assertEquals(60, $result);
الطريقة assertEquals تجعل من الممكن مقارنة قيمتين ، هنا 60 و $result . إذا قمت بإجراء الاختبار ، فستحصل على:

OK (1 test, 1 assertion)
ترى مرة أخرى تنفيذ اختبار وتأكيد. كل شيء سار بشكل جيد. قم بتغيير القيمة 60 إلى أخرى وستحصل على هذا:

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
أنت تعرف الآن المبدأ الأساسي للاختبار وما يمكن الحصول عليه كمعلومات في حالة الفشل.

تأكيدات وطريق الدعوة


التأكيدات
التأكيدات هي أداة الاختبار الأساسية. رأينا واحدة أعلاه وهناك الكثير غيرها. يمكنك العثور على القائمة الكاملة هنا .
هنا هو استخدام بعض التأكيدات واستخدام مساعد Laravel الذي نختبره في تمرير:

<?php
public function testBasicExample()
{
    $data = 'Je suis petit';
    $this->assertTrue(starts_with($data, 'Je'));
    $this->assertFalse(starts_with($data, 'Tu'));
    $this->assertSame(starts_with($data, 'Tu'), false);
    $this->assertStringStartsWith('Je', $data);
    $this->assertStringEndsWith('petit', $data);
}
عندما نطلق الاختبار ، سنحصل على:

OK (1 test, 5 assertions)
اختبار و 5 تأكيدات صحيحة.
دعوة الطريق واختبار الاستجابة
من السهل استدعاء طريق لاختبار الاستجابة. تعديل المسار الأساسي لهذا واحد:

<?php
Route::get('/', function()
{
    return 'coucou';
});
لذلك لدينا طلب مع عنوان url الأساسي واستجابة السلسلة "coucou . " سنختبر نجاح الطلب ، وأن هناك استجابة صحيحة وأن الرد coucou"" :

<?php
public function testBasicExample()
{
    $response = $this->call('GET', '/');
    $this->assertResponseOk();
    $this->assertEquals('coucou', $response->getContent());
}
التأكيد assertResponseOk يؤكد لنا أن الإجابة صحيحة. انها ليست تأكيدا ل PHPUnit ولكن واحدة محددة من Laravel . getContent تتيح لك الطريقة قراءة الرد. نحصل على:

OK (1 test, 2 assertions)

العرض و وحدات التحكم


العرض
ماذا لو ارجعنا العرض؟ ضع هذا الكود للطريق:

<?php
Route::get('/', function()
{
    return view('welcome')->with('message', 'Vous y êtes !');
});
أضف في هذا العرض هذا:

{ { $message }}
الآن هنا هو الاختبار:

<?php
public function testBasicExample()
{
    $response = $this->call('GET', '/');
    $view = $response->original;
    $this->assertEquals('Vous y êtes !', $view['message']);
}
نرسل الطلب ونحصل على الرد . ثم original تسمح لنا الطريقة باستعادة العرض. يمكننا بعد ذلك اختبار قيمة المتغير $message في العرض.
يقدم Laravel تأكيدًا آخر على فعل الشيء نفسه:

<?php
public function testBasicExample()
{
  $response = $this->call('GET', '/');
  $this->assertViewHas('message');
  $this->assertViewHas('message', 'Vous y êtes !');
}
التأكيد assertViewHas يسمح لك بالتحقق من أن العرض يستقبل البيانات بالفعل ، يمكنك أيضًا استخدام المُدخل الثاني للتحقق من قيمتها.
وحدات التحكم
إنشاء وحدة التحكم هذه:

<?php

namespace App\Http\Controllers;

class WelcomeController extends Controller
{
    public function index()
    {
        return view('welcome');
    }
}
يمكنك بسهولة اختبار تصرفات وحدة التحكم. قم بإنشاء هذا المسار لتطبيق وحدة التحكم أعلاه:

<?php
Route::get('welcome', 'WelcomeController@index');
تحقق من أنه يعمل (قد تحتاج إلى لمس العرض حيث قدمنا متغير).
احذف الملف ExampleTest.php الذي لم يعد مفيدًا لنا.
إنشاء بنية المجلد هذا والملف WelcomeControllerTest.php :
framework Laravel MVC
ملف الاختبار
و ضع هذا الكود في الملف WelcomeControllerTest.php  :

<?php

class WelcomeControllerTest extends TestCase
{

    public function testIndex()
    {
        $response = $this->action('GET', 'WelcomeController@index');
        $this->assertResponseOk();
    }

}
يوجد أمر Artisan لإنشاء فئة اختبار:

php artisan make:test WelcomeControllerTest
إذا قمت بإجراء الاختبار ، فيجب أن يكون جيدًا:

OK (1 test, 1 assertion)
لذا ، لاستدعاء إجراء وحدة تحكم ، استخدم فقط الطريقة action وحدد الفعل واسم وحدة التحكم واسم الطريقة المرتبطة. من ناحية أخرى ، من المهم تنظيم الاختبارات جيدًا لتجد طريقك. هناك طريقة جيدة للقيام بذلك وهي اعتماد نفس بنية المجلد كتلك المخططة للتطبيق.

عزل الاختبارات


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

"require-dev": {
    ...
    "mockery/mockery": "0.9.*"
},
يؤدي توفير هذا المكون فقط للتنمية إلى تبسيط التنفيذ للنشر. عادة يجب أن تجد هذا المكون في ملفاتك:
framework Laravel MVC
مكون السخرية
محاكاة فئة
سنرى الآن كيفية استخدامه ، لكن لذلك سنقوم بإعداد الكود المراد اختباره. لن يكون الأمر واقعياً للغاية ، لكنه مجرد فهم آلية تشغيل. استبدل كود وحدة التحكم WelcomeController  بهذا:

<?php

namespace App\Http\Controllers;

use Livre;

class WelcomeController extends Controller
{

    public function __construct()
    {
        $this->middleware('guest');
    }

    public function index(Livre $livre)
    {
        $livres = $livre->all();
        return view('welcome', compact('livres'));
    }

}
خططت لحقن نموذج في الوظيفة index . ليس من الحكمة للغاية بالضرورة من وجهة نظر مفاهيمية ، وسيكون من الأفضل الدخول في مستودع كما رأينا عدة مرات في هذه الدورة ، لكننا سنأخذ هذا الموقف البسيط لإجراء اختبار. إضافة إلى ذلك ،   لا تحتاج الفئة Livre إلى الوجود نظرًا لأننا لن نستخدمها حقًا.
تكمن الصعوبة هنا في وجود حقنة من الفئة. بما أننا نرغب في عزل الاختبارات ، فإن المثالية ستكون القدرة على محاكاة هذه الفئة. هذا هو بالضبط ما يفعله Mockery .
هنا فئة الاختبار التي سنستخدمها:

<?php

use Illuminate\Database\Eloquent\Collection;

class WelcomeControllerTest extends TestCase
{

    public function testIndex()
    {
        // Création de la collection
        $collection = new Collection;
        $i = 2;
        while($i--) {
            $collection->add((object) ['titre' => 'Titre' . $i]);
        }

        // Création Mock
        $mock = Mockery::mock('Livre');
        $mock ->shouldReceive('all')
              ->once()
              ->andReturn($collection);

        // Création lien
        $this->app->instance('Livre', $mock);

        // Action
        $response = $this->call('GET', 'welcome');

        // Assertions
        $this->assertResponseOk();
        $this->assertViewHas('livres');

    }

    public function tearDown()
    {
        Mockery::close();
    }

}
وإليك الكود المراد إضافته في طريقة العرض لجعله واقعيًا:

@foreach ($livres as $livre)
    <p>Titre : { { $livre->titre }}</p>
@endforeach

إذا قمت بإجراء الاختبار ستحصل على:

OK (1 test, 2 assertions)
اختبار واحد واثنين من التأكيدات الصحيحة. دعونا نلقي نظرة فاحصة على هذا الكود.
في البداية نقوم بإنشاء مجموعة من الكتب:

<?php
// Création de la collection
$collection = new Collection;
$i = 2;
while($i--) {
    $collection->add((object) ['titre' => 'Titre' . $i]);
}
ثم نقوم بإنشاء كائن Mock عن طريق مطالبته بمحاكاة الفئة Livre :

<?php
$mock = Mockery::mock('Livre');
ثم نحدد السلوك الذي نريده لهذا الكائن:

<?php
$mock ->shouldReceive('all')
      ->once()
      ->andReturn($collection);
يتم إخبارك أنه إذا كان يتلقى الاستدعاء إلى الأسلوب "all" ( shouldReceive) مرة واحدة ( once) فيجب عليه إرجاع المجموعة  $collection ( andReturn) . 
ثم نعلم حاوية Laravel بالاتصال بين فئة Livre‌ وكائن Mock الخاص بنا:

<?php
$this->app->instance('Livre', $mock);
هذه الطريقة للقول لـ Laravel : كلما احتجت إلى الفئة Livre  فيجب استخدام كائن Mock أولا .
ثم نقوم بهذا الإجراء ، هنا الطلب:

<?php
$response = $this->call('GET', 'welcome');
أخيرًا ، نقدم تأكيدين ، أحدهما للتحقق من أن لدينا إجابة صحيحة والثاني للتحقق من أن لدينا الكتب في الأفق:

<?php
$this->assertResponseOk();
$this->assertViewHas('livres');
أخيرًا ، نخطط لحذف كائن Mock في نهاية الاختبار بالطريقة tearDown التي يتم استخدامها لتنفيذ الإجراءات بعد الاختبار:

<?php
public function tearDown()
{
    Mockery::close();
}
أنت تعرف الآن مبدأ استخدام Mockery . هناك احتمالات واسعة مع هذا المكون. 
لا توجد حقًا قاعدة فيما يتعلق باعدادات الاختبارات ، أو فيما يتعلق بما يجب اختباره أم لا. الشيء المهم هو فهم كيفية القيام بها والحكم على ما هو مفيد أو لا وفقا للظروف. طريقة فعالة لتعلم كيفية إجراء الاختبارات مع فهم Laravel بشكل أفضل هي النظر في كيفية تصميم هذه الاختبارات .

اختبار التطبيق


يذهب Laravel إلى أبعد من ذلك في سهولة الاستخدام للاختبار من خلال تقديم إمكانية اختبار التطبيق بسهولة. 
ابدأ بـ Laravel جديد وقم بإضافة مصادقة باستخدام الأمر:

php artisan make:auth
قم بتوفير قاعدة بيانات لكي تعمل وأدخل المستخدم Toto ، مع البريد الإلكتروني "toto@chez.fr" وكلمة المرور "password" . سنقوم الآن بإنشاء اختبار لمعرفة ما إذا كان يمكن لهذا المستخدم المصادقة.
ابدأ بحذف الملف ExampleTest.php .
قم بإنشاء ملف جديد AuthControllerTest.php  باستخدام Artisan :

php artisan make:test AuthControllerTest
أكمل الكود كما يلي:

<?php

use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class AuthControllerTest extends TestCase
{
    /**
     * A basic test example.
     *
     * @return  void
     */
    public function testRegister()
    {
        $this->visit('/')
             ->click('Login')
             ->type('toto@chez.fr', 'email')
             ->type('password', 'password')
             ->press('Login')
             ->see('Dashboard');
    }
}
ترى أنه يقرأ مثل النثر! إذا قمت بإجراء الاختبار ، فستحصل على:

OK(2 tests, 5 assertions)
لن أفصل هنا جميع الاحتمالات ، يرجى الرجوع إلى الوثائق .

في الخلاصة


  • يستخدم Laravel PHPUnit لإجراء اختبارات الوحدة.
  • بالإضافة إلى أساليب PHPUnit ، لدينا مساعدين لدمج الاختبارات في تطبيق تم إنشاؤه بواسطة Laravel . 
  • يجعل مكون Mockery من الممكن محاكاة سلوك الفئة وبالتالي عزل الاختبارات جيدًا.
  • تقدم Laravel أوامر سهلة الاستخدام لاختبار التطبيق.