Сегодня я хочу предложить вам перевод статьи Federico Cargnelutti PHPUnit: Testing Zend Framework Controllers, посвященной первым шагам в модульном тестировании контроллеров действий Zend Framework.
Тестирование Веб-приложений — это комплексная задача, потому что веб-приложение создается из нескольких логических слоев. Модульное тестирование контроллера Zend Framework может быть весьма трудной задачей, особенно для тех, кто слабо знаком с Zend Framework.
Вы можете тестировать свои контроллеры действий использую Zend_Test и/или PHPUnit. Zend_Test позволяет вам имитировать запросы, передавать тестовые данные, контролировать вывод вашего приложения и в целом убедиться в том, что ваш код делает именно то, что должен делать. Вам решать, какой из них использовать. Если вы не можете выбрать один из них, то можете использовать оба. Если вы только знакомитесь с тестированием с помощью Zend_Test, то эта статья будет лучшим местом старта.
Фреймворк PHPUnit может показаться очень знакомым тем разработчикам, которые пришли из Java. Разработчики PHPUnit черпали вдохновение из JUnit — тестовом фреймворке для платформы Java, поэтому вы будете чувствовать себя как дома при использовании PHPUnit если вам уже приходилось сталкиваться с JUnit или одним из его клонов.
Конечно, никто не запрещает вам использовать системы бок о бок (даже в одном и том же приложении). В конце концов, большинство проектов так и будет использовать.
Использование PHPUnit
Во-первых, вам необходимо создать структуру каталогов:
app/
config/
controllers/
ExampleController.php
models/
views/
lib/
Zend/
public/
tests/
controllers/
AllTests.php
ExampleControllerTest.php
lib/
AllTests.php
bootstrap.php
Тестовый набор нуждается в некоторой информации об окружении, и обычно эта информация находится в файле bootstrap.php. Самым большим отличием этого файла от одного из из используемых в вашем приложении является то, что Фронт-контроллер не выполняет диспетчеризацию объекта запроса:
tests/bootstrap.php [ Открыть в Codepad ]
<?php
/* Start output buffering */
ob_start();
/* Report all errors directly to the screen for simple diagnostics in the dev environment */
error_reporting( E_ALL | E_STRICT );
ini_set('display_startup_errors', 1);
ini_set('display_errors', 1);
date_default_timezone_set('Europe/London');
/* Determine the root and library directories of the application */
$appRoot = dirname(__FILE__) . '/..';
$libDir = "$appRoot/lib";
$path = array($libDir, get_include_path());
set_include_path(implode(PATH_SEPARATOR, $path));
define('APPLICATION_PATH', $appRoot . '/app');
define('APPLICATION_ENVIRONMENT', 'dev');
require_once "Zend/Loader.php";
Zend_Loader::registerAutoload();
$front = Zend_Controller_Front::getInstance();
$front->throwExceptions(true);
$front->setParam('noViewRenderer', true);
$front->setParam('env', APPLICATION_ENVIRONMENT);
$front->setRequest(new Zend_Controller_Request_Http());
$front->returnResponse(true);
$router = $front->getRouter();
include APPLICATION_PATH . '/config/routes.php';
$router->addRoutes($routes);
$router->setParams($front->getParams());
$dispatcher = $front->getDispatcher();
$dispatcher->setParams($front->getParams());
$dispatcher->setResponse($front->getResponse());
$router->route($front->getRequest());
Обратите внимание! Отключение помощника ViewRenderer является не обязательным. Однако, вам должно быть известно, что использование класса Zend_Controller_Action_Helper_ViewRenderer может привести к снижению производительности. Подробнее об этом можно прочесть здесь.
Класс PHPUnit_Framework_TestSuite фреймворка PHPUnit позволяет вам организовать тесты в иерархические наборы тестов:
tests/AllTests.php [ Открыть в Codepad ]
<?php
require_once dirname(__FILE__) . '/bootstrap.php';
require_once dirname(__FILE__) . '/controllers/AllTests.php';
class AllTests
{
public static function main()
{
$parameters = array();
PHPUnit_TextUI_TestRunner::run(self::suite(), $parameters);
}
public static function suite()
{
$suite = new PHPUnit_Framework_TestSuite('My Application');
$suite->addTest(ControllersAllTests::suite());
return $suite;
}
}
AllTests::main();
tests/controllers/AllTests.php [ Открыть в Codepad ]
<?php
require_once dirname(__FILE__) . '/ExampleControllerTest.php';
class ControllersAllTests
{
public static function main()
{
PHPUnit_TextUI_TestRunner::run(self::suite());
}
public static function suite()
{
$suite = new PHPUnit_Framework_TestSuite('My Application - Controllers');
$suite->addTestSuite('ExampleControllerTestCase');
return $suite;
}
}
Написание модульных тестов
Из-за довольно странных причин эта часть не описана в документации. Вот что вам нужно сделать до написания теста:
- Подключить контроллер, который вы собираетесь тестировать.
- Расширить контроллер действий (унаследовавшись от него).
- Сбросить состояние экземпляра фронт-контроллера.
- Указать путь к тестируемому контроллеру действий.
- Установить объекты Запроса и Ответа.
- Создать экземпляр тестируемого объекта.
Пример:
tests/controllers/ExampleControllerTest.php [ Открыть в Codepad ]
<?php
require_once APPLICATION_PATH . '/controllers/ExampleController.php';
class ExampleControllerTest extends ExampleController
{
public function __construct($url = null)
{
$front = Zend_Controller_Front::getInstance();
$front->resetInstance();
$front->setControllerDirectory(APPLICATION_PATH . '/controllers');
$front->setRequest(new Zend_Controller_Request_Http($url));
$front->setResponse(new Zend_Controller_Response_Http());
parent::__construct($front->getRequest(), $front->getResponse());
}
}
Вся магия происходит внутри класса ExampleControllerTest. Он делает так, что контроллер действий думает, что был вызван фронт-контроллером в цикле диспетчеризации. Единственный путь сделать это — создание экземпляра контроллера действий без диспетчеризации запроса. Получение экземпляра контроллера действий дает вам больше контроля и гибкости, особенно при тестировании веб-сервисов.
А теперь пришло время создать наш первый тестовый набор. Тестовый набор это класс, наследуемый от PHPUnit_Framework_TestCase, содержащий тестовые методы, определяемые по префиксу “test” в названии метода.
require_once APPLICATION_PATH . '/controllers/ExampleController.php';
class ExampleControllerTest extends ExampleController
{
...
}
class ExampleControllerTestCase extends PHPUnit_Framework_TestCase
{
public function testDefaultAction()
{
$controller = new ExampleControllerTest();
$isDispatched = $controller->indexAction();
$this->assertTrue($isDispatched);
}
public function testFirstAction()
{
$url = 'http://localhost/example/first';
$controller = new ExampleControllerTest($url);
$controller->firstAction();
$errorMsg = $controller->getRequest()->getParam('error_message', null);
$this->assertEquals(null, $errorMsg);
}
public function testGetParameterName()
{
$url = 'http://localhost/example/first/fed';
$controller = new ExampleControllerTest($url);
$name = $controller->getRequest()->getParam('name', null);
$this->assertEquals('fed', $name);
}
public function testGetNameMethod()
{
$url = 'http://localhost/example/first/fed';
$controller = new ExampleControllerTest($url);
$name = $controller->getName();
$this->assertEquals('fed', $name);
}
}
Запуск тестов
federico@tests$ phpunit AllTests PHPUnit 3.3.8 by Sebastian Bergmann. ..... Time: 0 seconds OK (4 tests, 4 assertions)
Если тестирование завершится неудачно, то вы увидите подробную информацию о проваленном тесте. По желанию, вы можете подключить Phing в Hudson и автоматизировать выполнение этой задачи. Если есть вопросы — обращайтесь.
Метки: action controller, phpunit, unit test, zend framework




(3 голосов, средний: 3.67 из 5)
Спасибо, Олег. Очень полезно, узнал много нового.
Прекрасная и нужная статья.
А как тестить модели или имитировать заход например 10 пользователей на сайт?
Способ тестирования модели зависит от самой модели. Например, если модель обращается к БД, то надо организовывать тестовую БД с набором тестовых данных.
А вот имитация захода 10 посетителей — это не модульное, а нагрузочное тестирование, и реализуется оно другими инструментами.
Про модель понятно, я работал более года с Ruby on Rails там это все одним пакетом, пишешь разные тесты и все работает (для нагрузочного тоже тест пишешь а не используешь ab и еже с ним.). Вот и задумался как это сделать в зф.
2IgorN: Недавно наткнулся на информацию о том, что PHPUnit умеет выполнять нагрузочное тестирование. Но как именно это делать я еще не разбирался