اختبار برامج PHP باستخدام Codeception


عبد اللطيف ايمش

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

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

ما المشاكل التي تحلها الاختبارات؟

لنتعرّف أولًا على المشاكل التي يمكن حلها عبر الاختبارات. لن تتمكن من التخلص من جميع الأخطاء الظاهرة عند الاختبار، لكنك ستستطيع توصيف السلوك المتوقع في حالات الاختبار (test cases). انتبه إلى أنَّه قد توجد أخطاء ضمن حالات الاختبار التي تكتبها.

لكن كن على يقين أنَّ شيفرتك ستتغير فيما بعد (إما بتصحيح العلل، أو إضافة ميزات جديدة، حتى تصبح شيفرتك خاليةً من الأخطاء الموصوفة في حالة الاختبار). يمكن -إضافةً إلى ما سبق- استعمال حالات الاختبار المكتوبة بشكلٍ جيد في التوثيق لأنك سترى من خلالها ما هو السلوك المتوقع للبرنامج في حالات معيّنة. خلاصة القول أنَّ كتابة الاختبارات هي استثمارٌ مهمٌ تستفيد منه في المستقبل.

إذًا، ما هي الاختبارات التي يمكننا استعمالها في مشروعنا؟

1.jpg

اقتباس

لا تكفي اختبارات الوحدات (unit tests) الأساسية بمفردها، فيجب استعمال اختبارات لتكامل الأجزاء مع بعضها (integration tests) والاختبارات الوظيفية (function tests) واختبارات القبول (acceptance tests).

 

  • اختبارات الوحدات (unit tests): هي اختباراتٌ منخفضة المستوى (low-level) التي تتحقق من سلامة أجزاء صغيرة من شيفرتك، والتي تكون عادةً الدوال الموجودة في الأصناف (classes) الموجودة في مشروعك والمعزولة عن بعضها.
  • اختبارات التكامل (integration tests): تتحقق اختبارات التكامل من سلامة أداء جزءٍ من برنامجك والذي قد يحتوي على عدِّة أصناف أو دوال، إلا أنها تؤدي جميعًا غرضًا وحيدًا ألا وهو توفير ميزة معيّنة. يجب أن تتحقق هذه الاختبارات من كيفية تفاعل الأصناف مع بعضها.
  • الاختبارات الوظيفية (function tests): اختبار استجابة التطبيق لمختلف الطلبيات، كالتغيرات الحاصلة في قاعدة البيانات ...إلخ.
  • اختبارات القبول (acceptance tests): الغرض من هذه الاختبارات في أغلبية الحالات هو معرفة إن كان التطبيق يلبّي جميع متطلبات العميل.

للتوضيح، لنحاول شرح هذه العملية بتمثيلها على شيءٍ ملموس مثل المباني. تتألف بعض أنواع المباني من لبنات صغيرة تُشكِّل جدرانًا، ويجب أن تتحقق اشتراطاتٌ معيّنةٌ فيها، إذ يجب أن تستطيع أن تقاوم الحِمل المُطبَّق عليها، ويجب أن تكون لها أبعاد وحجم معيّن وهلم جرًا… تلك الأمور تسمى «اختبارات الوحدات» (unit tests). أما الفكرة من اختبارات الاندماج هي معرفة متانة وقوة الارتباط بين اللّبنات، وكيف تُشكِّل مع بعضها عنصرًا من عناصر المبنى (مثل الجدران). أما الاختبارات الوظيفية فيمكن ربطها بالاختبارات التي تُجرى على جدارٍ من جدران المبنى، مثل اختبار جودة العزل الحراري، ومعرفة إن كان بالإمكان مرور أشعة الشمس من النوافذ الموجودة في هذا الجدار. وأخيرًا، تتضمن اختبارات القبول إجراء فحص لكامل المبنى: كفتح الباب، والدخول إلى الغرفة، وإغلاق الباب، وتشغيل الضوء، والصعود إلى الطابق الثاني والنظر إلى الحديقة عبر النافذة…

تعرّف على Codeception

التقسيم السابق مشروطٌ بعدِّة عوامل، ويصعب مقاومة النزعة إلى خلط عدِّة أنواع من الاختبارات. يستعمل الكثير من المبرمجين اختبارات الوحدات ويدّعون أنها كافية؛ وكنتُ منهم، إلا أنني وجدتُ أنَّ استخدام عدِّة أنظمة لمختلف أنواع الاختبارات هو أمرٌ مرهقٌ ويستهلك وقتًا طويلًا. لذا قررتُ منذ فترة أن أعثر على شيءٍ أكثر فائدةً من مكتبة PHPUnit؛ أردتُ أن يتطور استخدامي لاختبارات الشيفرات، إلا أنني لم أرغب بقراءة وتعلم الكثير من صفحات التوثيق وأن أبحث عن الحلول الالتفافية للمشاكل التي تواجهني. ومن ثم اكتشفت وجود Codeception. كنتُ بادئ الأمر متشككًا ومرتابًا منه، وهذه طبيعتنا عندما نرى شيئًا جديدًا (الحق يقال: هذا المشروع موجودٌ منذ خمس سنوات، لذا لا يمكننا اعتباره «جديدًا»)، لكن بعد تجربته لعدة أيام، خَلِصتُ إلى أنَّ Codeception هو نظامٌ مفيدٌ جدًا.

قد تتساءل كيف تستطيع تثبيت Codeception؟ الأمر أبسط مما تتخيل:

$ composer require "codeception/codeception"
$ php vendor/bin/codecept bootstrap

بعد التثبيت ستجد مجلدًا جديدًا باسم test في مشروعك، وستجد داخله مجلداتٍ فرعيةً باسم acceptance و functional و unit، يبدو أننا نستطيع البدء بكتابة اختباراتنا، إذًا ما هي الخطوة القادمة؟ لنحاول الآن إضافة اختبار قبول لمثالٍ بسيط:


$ php vendor/bin/codecept generate:cept acceptance HelloWorld

يمكننا الآن فتح ملف اختبار القبول ذي المسار tests/acceptance/HelloWorldCept.php ونضع فيه المحتوى الآتي:


<?php
$I = new AcceptanceTester($scenario);
$I->wantTo('perform actions and see result');

المتغير الافتراضي المسمى ‎$I ليس مجرد حرف، وإنما هو ضمير متحدث يُشير إلى «الشخص» الذي يجري الاختبار: الذي يفتح صفحة تطبيقك أو أحد أصنافه ويفعل شيئًا ما ويريك النتيجة النهائية لأفعاله. إذ سترى ماذا فعل وما هو الخطأ الذي حدث. هذا هو سبب تسمية ذاك الكائن بالاسم ‎$I ولماذا تدعى الدوال الموجودة فيه wantTo()‎ و see()‎ و amOnPage()‎. لنفكر كشخصٍ يريد إجراء اختبارات للتحقق من عمل إحدى الصفحات. أوّل ما يخطر ببالنا هو فتح الصفحة والبحث عن عبارة معينة فيها، مما يعني أنَّ الصفحة متوافرة للزوار. يسهل كثيرًا فعل ذلك:


<?php
$I->amOnPage('/');
$I->see('Welcome');

ثم نستعمل الأمر الآتي لتشغيل اختبارات Codeception:


$ php vendor/bin/codecept run

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

2.jpg

اقتباس

حدث خطأٌ ما! هذه هي الفكرة الأساسية للاختبارات. عليك أن تنظر إلى الرسالة، وتعرف ما هو الخطأ، وتحاول إصلاحه.

لدينا اختبارٌ وحيدٌ وقد وَجَدَ خطأً:


Acceptance Tests (1)
Perform actions and see result (HelloWorldCept)                                                             Error

----------
1) Failed to perform actions and see result in HelloWorldCept (tests/acceptance/HelloWorldCept .php)
[GuzzleHttp\Exception\ConnectException] cURL error 6: Could not resolve host: localhost (see http://curl.haxx.se/libcurl/c/libcurl-errors.html) 

خلاصة الرسالة السابقة هي أنَّ الموقع في localhost غير متوافر. انظر إلى أوّل سطر من ملف الاختبار:


1. $I->amOnPage("/")

لنفتح ملف tests/acceptance.suite.yml ونعدّل قيمة url: http://localhost/‎ إلى رابطٍ آخر تتوافر عليه الصفحة. يتوافر على جهازي المحلي خادومٌ يحتوي على الصفحة التي نريد اختبارها url: https://local.codeception-article.com/‎. شغِّل الاختبار مرةً أخرى، ويجب أن تظهر عندك النتيجة الآتية:


Acceptance Tests (1) ---------------------------------------------------------------------------------------
Perform actions and   result (HelloWorldCept)                                                             Ok

تهانينا! لقد نجح اختبارنا. تتوافر العديد من الدوال للاختبار بجانب الدالة amOnPage()‎، لكنني عرضتُها لسهولة استعمالها. يمكن تقسيم دوال الاختبار الموجودة في Codeception إلى المجموعات الآتية:

  • دوال التفاعل مع الصفحة: fillField()‎ و selectOption()‎ و submitForm()‎ و click()‎.
  • دوال التحقق من وجود أشياء معيّنة في الصفحة: see()‎ و dontSee()‎ و seeElement()‎ و seeInCurrentUrl()‎ و seeCheckboxIsChecked()‎ و seeInField()‎ و seeLink()‎. يمكنك إضافة لاحقة (suffix) إلى الدوال السابقة عندما تحتاج استعمالها دون أن توقف الاختبار إن لم يُعثَر على العنصر الذي تختبر وجوده.
  • دوال التعامل مع الكعكات (cookies): setCookie()‎ و grabCookie()‎ و seeCookie()‎.
  • التعليقات وشرح حالة الاختبار: amGoingTo()‎ و wantTo()‎ و expect()‎، استخدم هذه الدوال لكي تشرح ما الذي يفعله الاختبار، مما يذكرك بالهدف منه.

فلو أردنا مثلًا أن نختبر صفحةً لاستعادة كلمة المرور عبر البريد الإلكتروني، فسنكتب ملفًا شبيهًا بالملف الآتي:


<?php
$I = new AcceptanceTester($scenario);
$I->wantTo('Test forgotten password functionality');
$I->amOnPage('/forgotten')
$I->see('Enter email');
$I->fillField('email', 'incorrect@email.com');
$I->click('Continue');
$I->expect('Reset password link not sent for incorrect email');
$I->see('Email is incorrect, try again');
$I->amGoingTo('Fill correct email and get link');
$I->see('Enter email');
$I->fillField('email', 'correct@email.com');
$I->click('Continue');
$I->expect('Reset password link sent for correct email');
$I->see('Please check your email for next instructions');

يبدو أنَّ الملف السابق يكفي لاختبار الصفحة، لكن ماذا لو كانت هنالك بعض الأقسام في الصفحة التي تُحمَّل عبر Ajax؟ كيف نستطيع أن نختبر تلك الصفحة؟ أجيبك أنَّ Codeception يستعمل مكتبة PhpBrowser المبنية على Symfony BrowserKit و Guzzle. أي لا تقلق، فالأمر سهلٌ وبسيط، ولن تحتاج إلا إلى استعمال curl لحل هذه المشكلة.

يمكنك أيضًا استخدام Selenium واختبار الصفحات عبر المتصفحات الحقيقية. وصحيحٌ أنَّ ذلك أبطأ، إلا أنك ستتمكن من اختبار سكربتات JavaScript أيضًا.

لكن أولًا عليك تثبيت إضافة Selenium، وتعديل ملف acceptance.suite.yml وإعادة بناء الصنف AcceptanceTester، ويمكنك بعد ذلك استخدام الدوال wait()‎ و waitForElement()‎؛ وستتمكن أيضًا من الحفاظ على وقتك ومواردك باستخدام الدالتين saveSessionSnapshot()‎ و loadSessionSnapshot()‎ اللتين تسمحان لك بحفظ حالة الجلسة الراهنة، وإجراء اختبارات جديدة على جلسات محفوظة مسبقًا، وقد تستفيد من هذا في بعض الحالات مثل اختبار عملية الاستيثاق من المستخدم.

الخلاصة هي أننا نستطيع اختبار العديد من الوظائف بسهولة وسرعة وكفاءة.

الاختبار الوظيفي

لننتقل الآن إلى إجراء اختبارات وظيفية على تطبيقنا:


$ php vendor/bin/codecept generate:cept functional HelloWorld

ومحتوى الملف:


<?php
$I = new FunctionalTester($scenario);
$I->amOnPage('/');
$I->see('Welcome');

انتظر قليلًا، ماذا يحدث!

لا يوجد خطأ في الشيفرة السابقة، حيث تُكتَب الاختبارات الوظيفية بنفس طريقة كتابة اختبارات التكامل، الفرق بينهما هو أنَّ الاختبارات الوظيفية تتفاعل مباشرةً مع تطبيقك، مما يعني أنَّك لن تحتاج إلى خادوم ويب لتشغيل اختبار وظيفي، وستستطيع اختبار أجزاء مختلفة من تطبيقك.

وصحيحٌ أنَّه لا يتوافر دعمٌ لجميع إطارات العمل، لكن قائمة الإطارات المدعومة كبيرة (Symfony، و Silex، و Phalcon، و Yii، و Zend Framework، و Lumen، و Laravel) ويجب أن تكون كافيةً لأغلبية الحالات ولمعظم المطورين. رجاءً راجع توثيق Codeception للحصول على قائمة بجميع الدوال المتوافرة وكيفية تفعيلها في ملف functional.suite.yml.

3.jpg

 

اقتباس

يدعم Codeception أغلبية إطارات العمل الرئيسية: مثل Symfony، و Silex، و Phalcon، و Yii، و Zend Framework، و Lumen، و Laravel

قبل أن ننتقل إلى اختبارات الوحدات، فدعني أنحرف قليلًا عن الموضوع وأحدثك قليلًا عن شيءٍ ربما لاحظتَه عند إنشاء الاختبارات:


$ php vendor/bin/codecept generate: cept acceptance HelloWorld

لاحظ الكلمة cept، واعلم أنَّ ما سبق ليس الطريقة الوحيدة لإنشاء الاختبارات، فهنالك أيضًا «اختبارات cest»، والفرق بينهما هو أنَّك تستطيع هيكلة عدِّة حالات وسيناروهات في نفس الصنف:


$ php vendor/bin/codecept generate:cest acceptance HelloWorld
<?php
class HelloWorldCest
{
    public function _before(AcceptanceTester $I)
    {
         $I->amOnPage('/forgotten')
    }

    public function _after(AcceptanceTester $I)
    {
    }

    // tests
    public function testEmailField(AcceptanceTester $I)
    {
	$I->see('Enter email');

    }
    public function testIncorrectEmail(AcceptanceTester $I)
    {
	$I->fillField('email', 'incorrect@email.com');
	$I->click('Continue');
	$I->see('Email is incorrect, try again');
    }
    public function testCorrectEmail(AcceptanceTester $I)
    {
	$I->fillField('email', 'correct@email.com');
	$I->click('Continue');
	$I->see('Please check your email for next instructions');
    }
}

ستُشغَّل الدالتان ‎_before()‎ و ‎_after()‎ قبل وبعد كل اختبار. وستُمرَّر نسخةٌ من الصنف AcceptanceTester إلى كل اختبار، لذا يمكنك استخدامه بنفس الآلية المتبعة في اختبارات cest.

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

اختبارات الوحدات

أخيرًا: اختبارات الوحدات. بُني Codeception على PHPUnit، أي أنَّك تستطيع استخدام الاختبارات المكتوبة لمكتبة PHPUnit، وذلك باستخدام الأمر الآتي:


$ php vendor/bin/codecept generate:phpunit unit HelloWorld

أو يمكنك ببساطة أن «ترث» (inherit) الاختبارات في ‎\PHPUnit_Framework_TestCase. لكن إن أردتَ شيئًا إضافيًا، فيمكنك تجربة اختبارات الوحدات التي يوفرها Codeception:


$ php vendor/bin/codecept generate:test unit HelloWorld
<?php

class HelloWorldTest extends \Codeception\TestCase\Test
{
    /**
     * @var \UnitTester
     */
    protected $tester;

    protected function _before()
    {
    }

    protected function _after()
    {
    }

    // tests
    public function testUserSave()
    {
	$user = User::find(1);
	$user->setEmail('correct@email.com');
	$user->save();
	$user = User::find(1);
	$this->assertEquals('correct@email.com', $user->getEmail());
    }
}

لا شيء يختلف عمّا عهدتَه من قبل، فالدالتان ‎_before()‎ و ‎_after()‎ هما بديلتا setUp()‎ و tearDown()‎، وستعملان قبل وبعد كل اختبار. الميزة الأساسية لهذا الاختبار هي القدرة على توسعة عملية الاختبار بتضمين وحدات (modules) التي يمكن تفعيلها في ملف unit.suite.yml:

  • الوصول إلى memcache وقواعد البيانات لتتبع التغيرات (قواعد MySQL و SQLite و PostgreSQL و MongoDB مدعومة).
  • اختبار تطبيقات REST/SOAP.
  • Queues

كل وحدة (module) لها ميزاتها، لذا من الأفضل أن تنظر إلى التوثيق وتجمع المعلومات الضرورية عنها قبل الانتقال إلى كتابة اختبارات تستعملها. يمكنك أيضًا استعمال حزمة Codeception/Specify (والتي يجب إضافتها إلى ملف composer.json) وتكتب وصفًا كالآتي:


<?php 
class HelloWorldTest extends \Codeception\TestCase\Test
{
    use \Codeception\Specify;
    private $user;
    protected function _before()
   {
	$this->user = User::find(1);
   }
    public function testUserEmailSave()
    {
	$this->specify("email can be stored", function() {
            	$this->user->setEmail('correct@email.com');
		$this->user->save();
		$user = User::find(1);
            	$this->assertEquals('correct@email.com', $user->getEmail());
        	});
    }
}

شيفرة PHP الموجودة ضمن الدوال المغلقة (closure functions) السابقة معزولةٌ، لذا لن تؤثر التعديلات داخلها على بقية شيفراتك. ستساعد عبارات الشرح بجعل الغاية من الاختبار واضحةً وتسهيل التعرف على الاختبارات التي فشلت. إضافةً إلى ما سبق، يمكنك استعمال الحزمة Codeception\Verify لكتابة اختبارات بشيفرة شبيهة بأسلوب BDD:


<?php
public function testUserEmailSave()
{
	verify($map->getEmail())->equals('correct@email.com');
}

ويمكنك بالطبع استعمال stub:


<?php
public function testUserEmailSave()
{
        $user = Stub::make('User', ['getEmail' => 'correct@email.com']);
        $this->assertEquals('correct@email.com', $user->getEmail());
}

الخلاصة: يساعد Codeception على توفير الوقت والجهد

ما الذي تتوقع الحصول عليه من Codeception؟ من هم الأشخاص المناسبون لاستعماله؟

4.jpg

 

اقتباس

يمكن للمطورين من جميع المستويات استخدام Codeception من المبتدئين حتى المحترفين، والموجودين بفرق برمجية بمختلف الأعداد.

برأيي الشخصي، Codeception مناسب لجميع أنواع الفرق: الكبيرة والصغيرة، والمبتدئين والمحترفين، والذين يستعملون أطر عمل والذين يكتبون برمجياتهم من الصفر، أي أنَّ Codeception صالحٌ لكل زمان ومكان (تقريبًا).

Codeception هو إطار عمل ناضج وموثّق جيدًا ويمكن توسعته باستعمال إضافات. وهو حديثٌ، ومبنيٌ على المكتبة الشهيرة PHPUnit، مما يطمئن المطورين الذين لا يريدون تجربة الكثير من المكتبات قبل الاستقرار. أداءُ هذه المكتبة جيدٌ، وهذا يعني أنها سريعة ولا تحتاج وقتًا ولا جهدًا كثيرًا، ومن السهل تعلمها، والتوثيق الرائع يجعل عملية التعلم تمر دون مشاكل.

من السهل جدًا تثبيت وضبط Codeception، مع أنه يحتوي على الكثير من الميزات المتقدمة. وصحيحٌ أنَّ أغلبية المستخدمين لا يحتاجون جميع (أو حتى أغلب) الميزات الموجودة فيه، إلا أنَّ ذلك يعتمد على ما تشاء فعله معه. يمكنك البدء بالأساسيات، ثم ستبدأ بالتعامل مع الميزات الإضافية عاجلًا أو آجلًا.

ترجمة -وبتصرّف- للمقال Jumpstart Your PHP Testing with Codeception لصاحبه Vasily Koval



2 اشخاص أعجبوا بهذا


تفاعل الأعضاء


اقتباس

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

 

3 اشخاص أعجبوا بهذا

شارك هذا التعليق


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


يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن