Якщо ви вивчаєте PHP більше року, то, напевно, вже стикалися з юніт тестуванням, і навіть писали тести, ну а в цій статті я наведу лише “рецепт” для швидкого приготування тестів на Zend Framework’е.

Установка

Для тестування додатків на Zend Framework’е використовується компонент самого фреймворку Zend_Test, який в свою чергу являється нащадком PHPUnit’a, і от його нам треба буде ще встановити, детально процес установки PHPUnit’a описаний на домашній сторінці проекту: Chapter 3. Installing PHPUnit, тут викладу лише короткий переказ:

Перший спосіб полягає у використанні PEAR інсталлера:

pear channel-discover pear.phpunit.de
pear install phpunit/PHPUnit

Другий – вручну:

  • завантажуємо архів з сторінки https://phpunit.de/ і розпаковуємо його в папку, яка включена в include_path (див. php.ini)
  • перейменовуємо phpunit.php у phpunit
  • міняємо рядок @[email protected] шлях PHP (наприклад, /usr/bin/php)
  • закидаємо цей файл до всіх бинарникам, і виконуємо команду chmod +x phpunit
  • у файлі PHPUnit/Util/Fileloader.php міняємо рядок @[email protected] шлях PHP (наприклад, /usr/bin/php)

Структура проекту

Наведу приклад структури каталогу для проекту створеного з використання Zend_Tool (щоб не заблукати, структура каталогу tests нагадує структуру всього додатки):

project
|– application
|– data
|– docs
| library–
|– public
`– tests
|– application
| |– models
| | `– PageTest.php
| |– controllers
| | |– IndexControllerTest.php
| | `– ErrorControllerTest.php
| |– ControllerTestCase.php
| `– bootstrap.php
| library–
| `– bootstrap.php
`– phpunit.xml

Хто забув – проект створюємо наступним чином:

zf create project zf-project

Конфігураційний файл phpunit.xml

Для організації тестування створимо конфігураційний файл phpunit.xml, phpunit необхідно буде запускати в папці tests:

$~/zf-project/tests/phpunit ./

Опис налаштувань див. у документації

./
/usr/share/php
../tests
../application
../library
../application
../application/Bootstrap.php

Ініціалізація у файлі bootstrap.php

У файлі phpunit.xml вказаний bootstrap файл, він відповідає за завантаження нашої програми, і лише трохи відрізняється від звичного index.php:

ControllerTestCase.php

Клас ControllerTestCase є предком для всіх тесткейсов:
bootstrap = array($this, ‘appBootstrap’);
parent::setUp();
}
public function appBootstrap()
{
// ініціалізуємо наше додаток
$this->_application = new Zend_Application(
APPLICATION_ENV,
APPLICATION_PATH . ‘/configs/application.ini’
);
$this->_application->bootstrap();
}
}

Написання Тестів

Перший млинець

Варто почати з мінімуму – перевіримо, що в нас тести запускаються і працюють, створимо простий тест:

class IndexControllerTest extends ControllerTestCase
{
public function testTestAction()
{
$this->assertTrue(true);
}
}

Якщо ми нічого не забули, то побачимо щось на кшталт:

PHPUnit 3.4.0beta3 by Sebastian Bergmann.
Time: 0 seconds
OK (1 tests, 1 assertions)
Generating code coverage report, this may take a moment.

Ще трохи млинців

Тепер трохи ускладнимо завдання – будемо перевіряти, чи туди ми потрапили, для цього будемо використовувати наступні методи:

  • dispatch – вказуємо URL куди йдемо
  • assertModule – перевіряємо той модуль, який нам потрібен
  • assertController – перевіряємо контролер
  • assertAction – перевіряємо екшен

class IndexControllerTest extends ControllerTestCase
{
public function testIndexAction()
{
$this->dispatch(‘/’);
$this->assertModule(‘default’);
$this->assertController(‘index’);
$this->assertAction(‘index’);
}
}

Теж саме зробимо для ErrorController’а:

class ErrorControllerTest extends ControllerTestCase
{
public function testErrorURL()
{
$this->dispatch(‘foo’);
$this->assertModule(‘default’);
$this->assertController(‘error’);
$this->assertAction(‘error’);
}
}

Запускаємо тести:

PHPUnit 3.4.0beta3 by Sebastian Bergmann.
Time: 0 seconds
OK (3 tests, 7 assertions)
Generating code coverage report, this may take a moment.

Можна тепер поглянути на звіт – для цього йдемо за адресою http://%project%/tests/reports/ і тама ми повинні побачити щось на кшталт:

Якщо ви нічого не побачили – значить – небудь директорія tests/reports не writable, або XDebug у вас не встановлений.

Перевіряємо DOM

Тепер створимо новий екшен:

zf create action about index

Додамо функціоналу для виводу повідомлення у view:

public function aboutAction()
{
// body action
$message = $this->_getParam(‘m’);
if ($message) {
$this->view->message = $message;
} else {
$this->view->message = “no message”;
}
}

Змінимо подання:

message ?>

Напишемо ще трохи тестів:

class IndexControllerTest extends ControllerTestCase
{
// перевіряємо що даний екшен доступний
public function testAboutAction()
{
$this->dispatch(‘/index/about’);
$this->assertModule(‘default’);
$this->assertController(‘index’);
$this->assertAction(‘about’);
}
// перевіряємо поведінка екшену за замовчуванням
public function testAboutNoMessageAction()
{
$this->dispatch(‘/index/about’);
$this->assertModule(‘default’);
$this->assertController(‘index’);
$this->assertAction(‘about’);
$this->assertResponseCode(200);
// на сторінці повинен бути присутнім елемент з ID=”messages” у кількості 1 штука
$this->assertQueryCount(‘#message’, 1);
// і в ньому повинен бути текст “no message”
$this->assertQueryContentContains(‘#message’, “no message”);
}
// перевіряємо поведінка екшену при вхідних параметрах
public function testAboutWithMessageAction()
{
$this->dispatch(‘/index/about/m/true-поля-поля’);
$this->assertModule(‘default’);
$this->assertController(‘index’);
$this->assertAction(‘about’);
$this->assertResponseCode(200);
$this->assertQueryCount(‘#message’, 1);
// текст повинен бути “true-поля-поля”
$this->assertQueryContentContains(‘#message’, “true-поля-поля”);
}
// альтернативний спосіб передачі параметрів у реквест
// даний тест дублює попередній
public function testAboutWithMessageAltAction()
{
$this->getRequest()
->setParams(array(“m” => “true-поля-поля”))
->setMethod(‘GET’);
$this->dispatch(‘/index/about/’);
$this->assertModule(‘default’);
$this->assertController(‘index’);
$this->assertAction(‘about’);
$this->assertResponseCode(200);
$this->assertQueryCount(‘#message’, 1);
$this->assertQueryContentContains(‘#message’, “true-поля-поля”);
}

Опис assert’ів за DOM’у можна знайти у документації:

  • Селектори CSS
  • XPath

Тестування аутентифікації користувачів

Для початку нам знадобиться таки написати просту систему аутентифікації, створимо новий контролер Login:

view->login = false;
// перевірка не залягання користувач
if (!Zend_Auth::getInstance()->getIdentity()) {
// беремо форму логіна і закидаємо в view
$form = $this->_getLoginForm();
$this->view->form = $form;
// якщо форма була відправлена, ми повинні перевірити її
if ($this->_request->isPost()) {
$formData = $this->_request->getPost();
// перевіряємо форму
if ($form->isValid($formData)) {
// перевіряємо вхідні дані
$result = $this->_authenticate($form->getValue(‘realm’), $form->getValue(‘username’), $form->getValue(‘password’));
if ($result->isValid()) {
// запам’ятаємо користувача на 2 тижні
if ($form->getValue(‘rememberMe’)) {
Zend_Session::rememberMe(60*60*24*14);
}
// відправляємо на головну
$this->_redirect(‘/’);
} else {
// failure: виводимо повідомлення про помилку
$this->view->error = ‘Authorization error. Please check login or/and password’;
}
} else {
$form->populate($formData);
}
}
} else {
$this->view->login = true;
$this->view->username = Zend_Auth::getInstance()->getIdentity();
}
}
// генерація форми логіна
private function _getLoginForm()
{
$form = new Zend_Form();
$form->setMethod(‘POST’);
$form->setName(‘userLoginForm’);
$username = new Zend_Form_Element_Text(‘username’);
$username>setLabel(‘User name’)
->setRequired(true)
->addFilter(‘StripTags’)
->addFilter(‘StringTrim’)
->addValidator(‘Alnum’)
->addValidator(‘StringLength’, false,
array(3,
24));
$password = new Zend_Form_Element_Password(‘password’);
$password)->setLabel(‘Password’)
->setRequired(true)
->setValue(null)
->addValidator(‘StringLength’, false,
array(6));
$realm = new Zend_Form_Element_Select(‘realm’);
$realm->setLabel(‘Role’)
->addMultiOptions(array(‘user’=>’User’, ‘admin’=>’Admin’))
->setRequired(true)
->setValue(‘user’);
$rememberMe = new Zend_Form_Element_Checkbox(‘rememberMe’);
$rememberMe->setLabel(‘Remember Me’);
$submit = new Zend_Form_Element_Submit(‘submit’);
$submit->setLabel(‘Login’);
$form->addElements(array($realm, $username, $password, $rememberMe, $submit));
return $form;
}
// аутентифікація – найпростіша – використовуючи Digest Adapter
protected function _authenticate($realm, $login, $password)
{
$authAdapter = new Zend_Auth_Adapter_Digest(APPLICATION_PATH . ‘/configs/auth’, $realm, $login, $password);
$result = $authAdapter->authenticate();
if ($result->isValid()) {
// success: зберігаємо роль користувача в Zend_Auth
Zend_Auth::getInstance()->getStorage()->write($authAdapter->getRealm());
}
return $result;
}
// разлогиниваемся
public function logoutAction()
{
Zend_Auth::getInstance()->clearIdentity();
$this->_redirect(‘/’);
}
}

В комплекті додамо ще й view:

login) :?>

Username

You are logged in as username ?>

Login

error)) :?>
escape($this->error);?>
form; ?>

Тепер необхідно написати тести для цих контролерів, але почну я з емуляції авторизації в юніт-тести – додам метод _doLogin в ControllerTestCase:

protected function _doLogin($realm, $login, $password)
{
$authAdapter = new Zend_Auth_Adapter_Digest(APPLICATION_PATH . ‘/configs/auth’, $realm, $login, $password);
$result = $authAdapter->authenticate();
if ($result->isValid()) {
// success: зберігаємо роль користувача в Zend_Auth
Zend_Auth::getInstance()->getStorage()->write($authAdapter->getRealm());
}
}

Увага! Для тестування не слід використовувати реальні дані, для даного прикладу краще використовувати тестовий файл автентифікації, або створити mock для класу Zend_Auth

Тепер варто перевірити як ми логинимся/логаутимся:

class LoginControllerTest extends ControllerTestCase
{
// логинимся “правильним” даними
public function testTrueUserLoginAction()
{
// емуляціях відправку форми
$this->getRequest()
->setMethod(‘POST’)
->setPost(array( “realm” => “user”,
“username” => “user”,
“password” => “123456”,
“rememberMe”=>1));
$this->dispatch(‘/login/’);
// аутентифікація повинна пройти успішно, ми повинні ідентифікуватися як user
$this->assertEquals(Zend_Auth::getInstance()->getIdentity(), ‘user’);
// ми повинні бути перенаправлені на головну сторінку
$this->assertRedirectTo(‘/’);
}
// логинимся “неправильним” даними
public function testFalseUserLoginAction()
{
$this->getRequest()
->setMethod(‘POST’)
->setPost(array( “realm” => “user”,
“username” => “user”,
“password” => “654321”,
“rememberMe”=>0));
$this->dispatch(‘/’);
// шукаємо в будинку елемент з ID=”error” і контентом ‘Authorization error. Please check login or/and password’
// краще використовувати assertQueryCount
$this->assertQueryContentContains(‘#error’, ‘Authorization error. Please check login or/and password’);
}
public function testLogoutAction()
{
// логинимся
$this->_doLogin(‘admin’, ‘admin’, ‘123456’);
// викликаємо логаут
$this->dispatch(‘/login/logout/’);
// тепер ми повинні бути “забуті” Zend_Auth’ом
$this->assertNull(Zend_Auth::getInstance()->getIdentity());
}
}

Напишемо ще один контролер – Admin – з дуже простою логікою – якщо не заходить адмін – то його має перенаправляє на сторінку denied:

class AdminController extends Zend_Controller_Action
{
public function indexAction()
{
// перевіряємо користувача на приналежність до адмінам
if (Zend_Auth::getInstance()->getIdentity() !== ‘admin’) {
// якщо ні – то відправляємо на сторінку помилки
$this->_forward(‘denied’,’error’);
}
}
}

Тести теж будуть простенькими:

class AdminControllerTest extends ControllerTestCase
{
public function testIndexAction()
{
$this->dispatch(‘/admin/’);
$this->assertModule(‘default’);
$this->assertController(‘error’);
$this->assertAction(‘denied’);
}
public function testIndexUnderAdminAction()
{
$this->_doLogin(‘admin’, ‘admin’, ‘123456’);
$this->dispatch(‘/admin/’);
$this->assertModule(‘default’);
$this->assertController(‘admin’);
$this->assertAction(‘index’);
}
}

Результат:

PHPUnit 3.4.0beta3 by Sebastian Bergmann.
Time: 2 seconds
OK (13 tests, 39 assertions)
Generating code coverage report, this may take a moment.

Завантажити

Ви можете скачати даний приклад за посиланням нижче, в архіві немає Zend Framework’a, приклад працює з версією 1.8.4, версія PHPUnit’a – 3.4.0 b:

DownloadlessonPHPUnit + ZF

Посилання

  • Матеріал до даної статті: Zend Framework and Unit testing
  • Скрін-каст: Unit Testing with the Zend Framework with Zend_Test and PHPUnit
  • Zend_Test – офіційний мануал в убогому стані
  • An Introduction to the Art of Unit Testing in PHP
  • PHPUnit: Testing Zend Framework Controllers ( переклад)
  • Setting up your Zend_Test test suites
  • Automatic testing of MVC applications created with Zend Framework
  • Автоматизоване тестування Zend Framework додатків
  • Топік на форумі zendframework.ru