يعد التطوير القائم على الاختبار واختبار الوحدة من أحدث الطرق لضمان استمرار عمل التعليمات البرمجية كما هو متوقع على الرغم من التعديلات والتعديلات الرئيسية. في هذه المقالة، ستتعلم كيفية وحدة اختبار كود PHP الخاص بك في طبقات الوحدة النمطية وقاعدة البيانات وواجهة المستخدم (UI).
إنها الساعة الثالثة صباحًا. كيف نعرف أن الكود الخاص بنا لا يزال يعمل؟
تعمل تطبيقات الويب على مدار 24 ساعة طوال أيام الأسبوع، لذا فإن السؤال حول ما إذا كان برنامجي لا يزال قيد التشغيل سيزعجني في الليل. لقد ساعدني اختبار الوحدة في بناء ثقة كافية في الكود الخاص بي حتى أتمكن من النوم جيدًا.
يعد اختبار الوحدة إطارًا لكتابة حالات الاختبار للتعليمات البرمجية الخاصة بك وتشغيل هذه الاختبارات تلقائيًا. التطوير المبني على الاختبار هو أسلوب اختبار الوحدة استنادًا إلى فكرة أنه يجب عليك أولاً كتابة الاختبارات والتحقق من أن هذه الاختبارات يمكنها العثور على الأخطاء، وعندها فقط تبدأ في كتابة التعليمات البرمجية التي تحتاج إلى اجتياز هذه الاختبارات. عند اجتياز جميع الاختبارات، تكون الميزة التي قمنا بتطويرها قد اكتملت. تكمن قيمة اختبارات الوحدات هذه في أنه يمكننا تشغيلها في أي وقت — قبل التحقق من الكود، أو بعد تغيير كبير، أو بعد النشر على نظام قيد التشغيل.
اختبار وحدة PHP
بالنسبة إلى PHP، إطار اختبار الوحدة هو PHPUnit2. يمكن تثبيت هذا النظام كوحدة PEAR باستخدام سطر أوامر PEAR: % pear install PHPUnit2.
بعد تثبيت إطار العمل، يمكنك كتابة اختبارات الوحدة عن طريق إنشاء فئات اختبار مشتقة من PHPUnit2_Framework_TestCase.
اختبار وحدة الوحدة
لقد وجدت أن أفضل مكان لبدء اختبار الوحدة هو وحدة منطق الأعمال الخاصة بالتطبيق. أنا أستخدم مثالًا بسيطًا: هذه دالة تجمع رقمين. لبدء الاختبار، نكتب أولاً حالة الاختبار كما هو موضح أدناه.
القائمة 1. TestAdd.php
<?phprequire_once 'Add.php';require_once 'PHPUnit2/Framework/TestCase.php';class TestAdd Extends PHPUnit2_Framework_TestCase{ function test1() { $this->assertTrue( add( 1, 2 ) == 3 } function test2() { $this->assertTrue( add( 1, 1 ) == 2 }}?>
تحتوي فئة TestAdd هذه على طريقتين، وكلاهما يستخدم بادئة الاختبار. تحدد كل طريقة اختبارًا، والذي يمكن أن يكون بسيطًا مثل القائمة 1 أو معقدًا جدًا. في هذه الحالة، نؤكد ببساطة أن 1 زائد 2 يساوي 3 في الاختبار الأول، و1 زائد 1 يساوي 2 في الاختبار الثاني.
يقوم نظام PHPUnit2 بتعريف طريقة AssurTrue()، والتي تُستخدم لاختبار ما إذا كانت قيمة الشرط الموجودة في المعلمة صحيحة. ثم قمنا بكتابة وحدة Add.php، والتي أعطت في البداية نتائج غير صحيحة.
القائمة 2. Add.php
<?phpfunction add( $a, $b ) { return 0 }?>
الآن عند تشغيل اختبارات الوحدة، يفشل كلا الاختبارين.
القائمة 3. حالات فشل الاختبار
% phpunit TestAdd.phpPHPUnit 2.2.1 بواسطة Sebastian Bergmann.FFTime: 0.0031270980834961 كان هناك فشلان: 1) test1(TestAdd)2) test2(TestAdd)FAILURES!!!تشغيل الاختبارات: 2، حالات الفشل: 2، الأخطاء: 0، الاختبارات غير المكتملة: 0.
أعلم الآن أن كلا الاختبارين يعملان بشكل جيد. لذلك، يمكن تعديل الدالة add() لتفعل الشيء الفعلي.
تم اجتياز كلا الاختبارين الآن.
<?phpfunction add( $a, $b ) { return $a+$b }?>
القائمة 4. تم اجتياز الاختبار
% phpunit TestAdd.phpPHPUnit 2.2.1 بواسطة Sebastian Bergmann...الوقت: 0.0023679733276367OK (اختباران)%
بالرغم من ذلك هذا المثال للتطوير المبني على الاختبار بسيط للغاية، لكن يمكننا فهم أفكاره. قمنا أولاً بإنشاء حالة الاختبار وكان لدينا تعليمات برمجية كافية لإجراء الاختبار، ولكن النتيجة كانت خاطئة. ثم نتحقق من فشل الاختبار بالفعل، ثم ننفذ الكود الفعلي لنجاح الاختبار.
أجد أنه أثناء تنفيذ التعليمات البرمجية، أواصل إضافة التعليمات البرمجية حتى أحصل على اختبار كامل يغطي جميع مسارات التعليمات البرمجية. في نهاية هذه المقالة، ستجد بعض النصائح حول الاختبارات التي يجب كتابتها وكيفية كتابتها.
اختبار قاعدة البيانات
بعد اختبار الوحدة، يمكن إجراء اختبار الوصول إلى قاعدة البيانات. يطرح اختبار الوصول إلى قاعدة البيانات مسألتين مثيرتين للاهتمام. أولاً، يجب علينا استعادة قاعدة البيانات إلى نقطة معروفة قبل كل اختبار. ثانيًا، انتبه إلى أن هذا الاسترداد قد يتسبب في تلف قاعدة البيانات الموجودة، لذا يجب علينا الاختبار على قاعدة بيانات غير إنتاجية، أو الحرص على عدم التأثير على محتوى قاعدة البيانات الموجودة عند كتابة حالات الاختبار.
يبدأ اختبار وحدة قاعدة البيانات من قاعدة البيانات. لتوضيح هذه المشكلة، نحتاج إلى استخدام النمط البسيط التالي.
القائمة 5. Schema.sql
DROP TABLE إذا كان المؤلفون موجودين؛ إنشاء مؤلفي الجدول (المعرف MEDIUMINT NOT NULL AUTO_INCREMENT، name TEXT NOT NULL، PRIMARY KEY (id))؛
القائمة 5 هي جدول مؤلفين، وكل سجل له معرف مرتبط.
بعد ذلك، يمكنك كتابة حالات الاختبار.
القائمة 6. TestAuthors.php
<?phprequire_once 'dblib.php';require_once 'PHPUnit2/Framework/TestCase.php';class TestAuthors Extends PHPUnit2_Framework_TestCase{ function test_delete_all() { $this->assertTrue( Authors::delete_all() ); } function test_insert() { $this->assertTrue( Authors::delete_all() ); $this->assertTrue( Authors::insert( 'Jack' ) } function test_insert_and_get() { $this->assertTrue( Authors ::delete_all() ); $this->assertTrue( Authors::insert( 'Jack' ) ); $this->assertTrue( Authors::insert( 'Joe' ) ); ; $this->assertTrue( $found != null ); $this->assertTrue( count( $found ) == 2 }}?>
تغطي هذه المجموعة من الاختبارات حذف المؤلفين من الجدول وإدراج المؤلفين في الجدول بالإضافة إلى وظائف مثل إدراج المؤلف مع التحقق من وجود المؤلف. هذا اختبار تراكمي، والذي أجده مفيدًا جدًا للعثور على الأخطاء. ومن خلال ملاحظة الاختبارات التي تنجح وتلك التي لا تنجح، يمكنك اكتشاف الأخطاء بسرعة ومن ثم فهم الاختلافات بشكل أكبر.
يتم عرض إصدار رمز الوصول إلى قاعدة بيانات PHP dblib.php الذي أدى في الأصل إلى الفشل أدناه.
القائمة 7. dblib.php
<?phprequire_once('DB.php'); :Connect( $dsn, array() ); if (PEAR::isError($db)) { die($db->getMessage()}); } public static function Insert( $name ) { return false; } public static function get_all() { return null }}?>
تنفيذ اختبارات الوحدة على الكود في القائمة 8 سيُظهر فشل الاختبارات الثلاثة:
List 8.dblib. php
% phpunit TestAuthors.phpPHPUnit 2.2.1 بواسطة Sebastian Bergmann.FFFTime: 0.007500171661377 كانت هناك 3 حالات فشل: 1) test_delete_all(TestAuthors)2) test_insert(TestAuthors)3) test_insert_and_get(TestAuthors)FAILURES!!! تشغيل الاختبارات: 3، الفشل: 3، الأخطاء: 0، الاختبارات غير المكتملة: 0.%
الآن يمكننا البدء في إضافة الكود للوصول إلى قاعدة البيانات بشكل صحيح - طريقة تلو الأخرى - حتى تنجح الاختبارات الثلاثة جميعها. يتم عرض الإصدار النهائي من كود dblib.php أدناه.
القائمة 9. أكمل dblib.php
<?phprequire_once('DB.php');class Authors{ public static function get_db() { $dsn = 'mysql://root:password@localhost/unitdb'; $db =& DB ::Connect( $dsn, array() ); if (PEAR::isError($db)) { die($db->getMessage())); = Authors::get_db(); $sth = $db->prepare( 'DELETE FROM Authors' ); $db->execute( $sth } return true; Authors::get_db(); $sth = $db->prepare( 'INSERT INTO Authors VALUES (null,?)' ); $db->execute( $sth, array( $name ) ); public static function get_all() { $db = Authors::get_db(); $res = $db->query( "SELECT * FROM Authors" ); while( $res->fetchInto( $row ) ) { $rows []= $row } return $rows; }}?>
اختبار HTML
الخطوة التالية في اختبار تطبيق PHP بالكامل هي اختبار واجهة لغة ترميز النص التشعبي (HTML) الأمامية. لإجراء هذا الاختبار، نحتاج إلى صفحة ويب مثل تلك الموجودة أدناه.
القائمة 10. TestPage.php
<?phprequire_once 'HTTP/Client.php';require_once 'PHPUnit2/Framework/TestCase.php';class TestPage Extends PHPUnit2_Framework_TestCase{ function get_page( $url ) { $client = new HTTP_Client(); ->get( $url ); $resp = $client->currentResponse(); return $resp['body'] } function test_get() { $page = TestPage::get_page( 'http://localhost/unit' /add.php' ); $this->assertTrue( strlen( $page ) > 0 ); $this->assertTrue( preg_match( '/<html>/', $page ) == } ); ) { $page = TestPage::get_page( 'http://localhost/unit/add.php?a=10&b=20' ); $this->assertTrue( strlen( $page ) > 0 $this->); AssureTrue( preg_match( '/<html>/', $page ) == 1 ); preg_match( '/<span id="result">(.*?)</span>/', $page, $out ); $this->assertTrue( $out[1]=='30' }}?>
يستخدم هذا الاختبار وحدة عميل HTTP التي يوفرها PEAR. أجدها أبسط قليلاً من مكتبة PHP Client URL (CURL) المضمنة، ولكن يمكن أيضًا استخدام الأخيرة.
يوجد اختبار يتحقق من الصفحة التي تم إرجاعها ويحدد ما إذا كانت تحتوي على HTML. يطلب الاختبار الثاني مجموع 10 و20 عن طريق وضع القيمة في عنوان URL المطلوب، ثم يتحقق من النتيجة في الصفحة التي تم إرجاعها.
يظهر رمز هذه الصفحة أدناه.
القائمة 11. TestPage.php
<html><body><form><input type="text" name="a" value="<?php echo($_REQUEST['a']); ?>" /> + <input type="text" name="b" value="<?php echo($_REQUEST['b']); ?>" /> =<span id="result"><?php echo($_REQUEST ['a']+$_REQUEST['b']); ?></span><br/><input type="submit" value="Add" /></form></body></html >
هذه الصفحة بسيطة إلى حد ما. يعرض حقلا الإدخال القيم الحالية المقدمة في الطلب. يُظهر نطاق النتيجة مجموع هاتين القيمتين. يمثل الترميز جميع الاختلافات: فهو غير مرئي للمستخدم، ولكنه مرئي لاختبارات الوحدة. لذلك لا تحتاج اختبارات الوحدة إلى منطق معقد للعثور على هذه القيمة. وبدلاً من ذلك، فإنه يسترد قيمة علامة معينة. بهذه الطريقة، عندما تتغير الواجهة، طالما أن الامتداد موجود، فسيتم اجتياز الاختبار.
كما كان من قبل، اكتب حالة الاختبار أولاً ثم قم بإنشاء نسخة فاشلة من الصفحة. نقوم باختبار الفشل ثم نقوم بتعديل محتوى الصفحة لجعله يعمل. النتائج هي كما يلي:
القائمة 12. فشل الاختبار، ثم تعديل الصفحة
% phpunit TestPage.phpPHPUnit 2.2.1 بواسطة سيباستيان بيرجمان...الوقت: 0.25711488723755OK (اختباران)%
يمكن أن ينجح كلا الاختبارين، مما يعني أن الاختبار الكود يعمل بشكل جيد.
عند إجراء اختبارات على هذا الرمز، يتم تشغيل جميع الاختبارات بدون مشكلة، لذلك نعلم أن الكود الخاص بنا يعمل بشكل صحيح.
لكن اختبار واجهة HTML الأمامية به عيب: JavaScript. يسترد كود عميل بروتوكول نقل النص التشعبي (HTTP) الصفحة، لكنه لا ينفذ JavaScript. لذا، إذا كان لدينا الكثير من التعليمات البرمجية في JavaScript، فيجب علينا إنشاء اختبارات وحدة على مستوى وكيل المستخدم. أفضل طريقة وجدتها لتحقيق هذه الوظيفة هي استخدام وظيفة طبقة التشغيل الآلي المضمنة في Microsoft® Internet Explorer®. باستخدام البرامج النصية لـ Microsoft Windows® المكتوبة بلغة PHP، يمكنك استخدام واجهة Component Object Model (COM) للتحكم في Internet Explorer للتنقل بين الصفحات، ثم استخدام أساليب Document Object Model (DOM) للعثور على الصفحات بعد تنفيذ إجراءات مستخدم محددة فيها .
هذه هي الطريقة الوحيدة التي أعرفها لوحدة اختبار كود JavaScript للواجهة الأمامية. أعترف أنه ليس من السهل الكتابة والصيانة، وأن هذه الاختبارات يتم كسرها بسهولة عند إجراء تغييرات طفيفة على الصفحة.
ما هي الاختبارات التي يجب كتابتها وكيفية كتابتها
عند كتابة الاختبارات، أحب أن أغطي السيناريوهات التالية:
جميع الاختبارات الإيجابية
تضمن هذه المجموعة من الاختبارات أن كل شيء يعمل كما نتوقع.
تستخدمجميع الاختبارات السلبية
هذه الاختبارات واحدًا تلو الآخر للتأكد من اختبار كل فشل أو شذوذ.
اختبار التسلسل الإيجابي
تضمن هذه المجموعة من الاختبارات أن المكالمات في التسلسل الصحيح تعمل كما نتوقع.
اختبار التسلسل السلبي
تضمن هذه المجموعة من الاختبارات فشل المكالمات عندما لا يتم إجراؤها بالترتيب الصحيح.
اختبار الحمل،
عند الاقتضاء، يمكن إجراء مجموعة صغيرة من الاختبارات لتحديد ما إذا كان أداء هذه الاختبارات ضمن توقعاتنا. على سبيل المثال، يجب إكمال 2000 مكالمة خلال ثانيتين.
اختبارات الموارد
تضمن هذه الاختبارات أن واجهة برمجة التطبيقات (API) تقوم بتخصيص الموارد وتحريرها بشكل صحيح - على سبيل المثال، استدعاء واجهة برمجة التطبيقات المستندة إلى الملفات المفتوحة والكتابة والإغلاق عدة مرات متتالية للتأكد من عدم وجود ملفات مفتوحة.
اختبارات رد الاتصال
بالنسبة لواجهات برمجة التطبيقات التي تحتوي على أساليب رد اتصال، تضمن هذه الاختبارات تشغيل التعليمات البرمجية بشكل طبيعي في حالة عدم تحديد وظيفة رد اتصال. بالإضافة إلى ذلك، يمكن لهذه الاختبارات أيضًا التأكد من أن التعليمات البرمجية لا تزال تعمل بشكل طبيعي عند تحديد وظائف رد الاتصال ولكن وظائف رد الاتصال هذه تعمل بشكل غير صحيح أو تولد استثناءات.
فيما يلي بعض الأفكار حول اختبار الوحدة. لدي بعض الاقتراحات حول كيفية كتابة اختبارات الوحدة:
لا تستخدم البيانات العشوائية
.على الرغم من أن إنشاء بيانات عشوائية في الواجهة قد يبدو فكرة جيدة، إلا أننا نريد تجنب القيام بذلك لأن تصحيح هذه البيانات قد يصبح صعبًا للغاية. إذا تم إنشاء البيانات بشكل عشوائي في كل مكالمة، فقد يحدث خطأ في اختبار واحد ولكن ليس في اختبار آخر. إذا كان الاختبار يتطلب بيانات عشوائية، فيمكنك إنشاؤها في ملف واستخدام هذا الملف في كل مرة تقوم فيها بتشغيله. باستخدام هذا الأسلوب، نحصل على بعض البيانات "المزعجة"، ولكن لا يزال بإمكاننا تصحيح الأخطاء.
الاختبارات الجماعية
يمكننا بسهولة تجميع آلاف الاختبارات التي يستغرق تنفيذها عدة ساعات. لا حرج في ذلك، ولكن تجميع هذه الاختبارات يسمح لنا بإجراء مجموعة من الاختبارات بسرعة والتحقق من المخاوف الرئيسية، ثم تشغيل مجموعة الاختبارات الكاملة ليلاً.
كتابة واجهات برمجة التطبيقات القوية والاختبارات القوية
من المهم كتابة واجهات برمجة التطبيقات والاختبارات بحيث لا تنكسر بسهولة عند إضافة وظائف جديدة أو تعديل الوظائف الموجودة. لا توجد عصا سحرية عالمية، ولكن القاعدة الأساسية هي أن الاختبارات التي "تتأرجح" (تفشل أحيانًا، وتنجح أحيانًا، مرارًا وتكرارًا) يجب التخلص منها بسرعة.
اختبار
الوحدة له أهمية كبيرة بالنسبة للمهندسين. إنها الأساس لعملية التطوير الرشيقة (التي تركز بشدة على البرمجة لأن التوثيق يتطلب بعض الأدلة على أن الكود يعمل وفقًا للمواصفات). توفر اختبارات الوحدة هذه الأدلة. تبدأ العملية باختبارات الوحدة، التي تحدد الوظيفة التي يجب أن تنفذها التعليمات البرمجية ولكنها لا تفعل ذلك حاليًا. لذلك، ستفشل جميع الاختبارات في البداية. ثم عندما يقترب الكود من الاكتمال، يتم اجتياز الاختبارات. عندما يتم اجتياز جميع الاختبارات، يصبح الكود مكتملًا للغاية.
لم أكتب مطلقًا تعليمات برمجية كبيرة أو قمت بتعديل مجموعة كبيرة أو معقدة من التعليمات البرمجية دون استخدام اختبارات الوحدة. عادةً ما أكتب اختبارات الوحدة للكود الموجود قبل تعديله، فقط للتأكد من أنني أعرف ما أقوم بكسره (أو عدم كسره) عندما أقوم بتعديل الكود. وهذا يمنحني ثقة كبيرة في أن الكود الذي أقدمه لعملائي يعمل بشكل صحيح - حتى الساعة 3 صباحًا.