Тестируем приложения на базе Zend Framework
Думаю мало кого из разработчиков можно удивить терминами “модульные тесты” и “разработка через тестирование”. Не буду здесь читать проповедь о пользе модульного тестирования и вреде нетестирования. Статья посвящена настройке среды для тестирования приложений на базе Zend Framework. Надеюсь, она поможет начинающим tdd-шникам сэкономить час-другой рабочего времени.
Потребуется
- Zend Framework и приложение на его основе, для которого и будем писать тесты (самая последня версия Zend Framework на данный момент - 1.0.0 RC2);
- TESTS_RUNNER - пакет из фреймворка Limb;
- SimpleTest - фреймоворк для модульного тестирования (т.к. мы будем использовать TESTS_RUNNER, который уже содержит в себе SimpleTest, то скачивать этот фреймворк отдельно не обязательно);
Структура приложения
Мое приложение имеет такую структуру:
--используемые библиотеки
library/
zend_framework/
test_runner/
...
lib/
simpletest/
...
--непосредственно приложение
mysite/
application/
admin/
controllers/
models/
templates/
configuration/
user/
controllers/
models/
User.php
templates/
docs/
tests/
runtests.php
index.php
tests/
application/
models/
cases/
var/
setup.php
Сперва, рассмотрим внутренности папки mysite.
Итак, папка application - ядро приложения, из соображений безопасности я размещаю ее вне папки docs (доступной для посетителей сайта). Внутри application содержится папка admin (для скриптов администраторской части сайта), а в ней, в свою очередь, папки для контроллеров(controllers), моделей(models), шаблонов(templates).
Содержание папки user полностью аналогично admin, здесь хранятся контроллеры, модели и шаблоны для пользовательской части. В configuration я храню конфигурационные файлы(данные для подключения к базе данных, пути к используемым библиотекам и т.д.) Папка docs содержит страницы видимые для посетителей сайта. Папка tests, как несложно догадаться, специально для тестов.
Дальше все просто, в zend_framework храним сам Zend Framework, а в test_runner кладем свежескачанный TESTS_RUNNER.
Настройка конфигурационных файлов
Вкратце о конфигурационных файлах. Их получилось достаточно много.
mysite/application/configuration/database.ini (настройка доступа к БД):
1 2 3 4 5 6 7 | [local] db.adapter = PDO_MYSQL db.config.host = localhost db.config.username = root db.config.password = db.config.dbname = languroo db.config.profiler = true |
mysite/application/configuration/setup.php (общий конфигурационный файл для админ и пользовательской частей):
1 2 3 4 5 6 7 8 9 10 | error_reporting(E_ALL | E_STRICT); date_default_timezone_set('Europe/Kiev'); //set include path for site set_include_path('.' . PATH_SEPARATOR . '/var/www/hosts/library/ZendFramework-1.0.0-RC2/library' . PATH_SEPARATOR . dirname(__FILE__) . '/../library/' . PATH_SEPARATOR . get_include_path() ); require_once('Zend/Loader.php'); |
mysite/application/configuration/user_setup.php
(конфигурационный файл для пользовательской части, аналогичный надо создать для админ части):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | //подключаем общий конфиг файл include_once(dirname(__FILE__) . '/setup.php'); set_include_path('.' . PATH_SEPARATOR . dirname(__FILE__) . '/../user/models' . PATH_SEPARATOR . get_include_path() ); //загрузка необходимых классов Zend Framework Zend_Loader::loadClass('Zend_Controller_Front'); Zend_Loader::loadClass('Zend_Controller_Router_Rewrite'); Zend_Loader::loadClass('Zend_View'); Zend_Loader::loadClass('Zend_Config_Ini'); Zend_Loader::loadClass('Zend_Db'); Zend_Loader::loadClass('Zend_Db_Table'); Zend_Loader::loadClass('Zend_Registry'); Zend_Loader::loadClass('Zend_Debug'); Zend_Loader::loadClass('Zend_Auth'); Zend_Loader::loadClass('Zend_Session'); //загружаем конфигурацию базы данных $config = new Zend_Config_Ini( dirname(__FILE__) . '/database.ini', 'local'); $registry = Zend_Registry::getInstance(); $registry->set('config', $config); //прописываем базу данных $db = Zend_Db::factory($config->db->adapter, $config->db->config->toArray()); Zend_Db_Table::setDefaultAdapter($db); $registry->set('db', $db); |
mysite/tests/setup.php (конфигурационный файл для тестов):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | require_once(dirname(__FILE__) . '/../application/configuration/user_setup.php'); //путь к пакету TESTS_RUNNER define('TEST_RUNNER_PATH', dirname(__FILE__) . '/../../library/tests_runner'); define('TEST_DIR_PATH', dirname(__FILE__)); define('LIMB_VAR_DIR', dirname(__FILE__) . '/var'); set_include_path('.' . PATH_SEPARATOR . dirname(__FILE__) . '/../application/' . PATH_SEPARATOR . dirname(__FILE__) . '/application/' . PATH_SEPARATOR . dirname(__FILE__) . '/' . PATH_SEPARATOR . get_include_path() ); //Подключение файлов необходимых для работы TESTS_RUNNER require_once(TEST_RUNNER_PATH . '/common.inc.php'); require_once(TEST_RUNNER_PATH . '/src/lmbTestShellUI.class.php'); require_once(TEST_RUNNER_PATH . '/src/lmbTestWebUI.class.php'); require_once(TEST_RUNNER_PATH . '/src/lmbTestTreeDirNode.class.php'); |
И наконец, в каталоге mysite/docs/tests создадим файл runtests.php (для запуска тестов через веб-интерфейс):
1 2 3 4 5 6 7 8 9 10 | require_once(dirname(__FILE__) . '/../../tests/setup.php'); $node = new lmbTestTreeDirNode(TEST_DIR_PATH . '/cases'); if(PHP_SAPI == 'cli') $ui = new lmbTestShellUI($node); else $ui = new lmbTestWebUI($node); $ui->run(); |
Тестирование
Перейдем непосредственно к тестированию. Проще всего тестировать классы модели. Так как основная цель статьи показать в работе связку Zend Framework c тестовой средой SimpleTest и надстройкой TESTS_RUNNER, поэтому, в этом примере я упущу написание контроллера и создание шаблонов и опишу только тестирование единственного метода в единственном классе модели приложения
Итак начнем с тестов. В каталоге tests содержится подкаталог application, который полностью повторяет структуру каталога mysite/application.
Файл mysite/test/application/user/models/UserTest.php содержит класс для тестирования класса (извините за тавтологию) User, который находится в файле mysite/application/user/models/User.php.
Следуя концепции TDD, сперва начнем писать именно тесты.
mysite/test/application/user/models/UserTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | require_once ('user/models/User.php'); class UserTests extends UnitTestCase { function testInsertEmptyUsername() { $user = &new User(); $user_data = array('nickname'=>'', 'gender'=>'male', 'country'=>'us', 'password'=>'new', 'birth_date'=>'2007-05-31', 'email'=>'<a href="mailto:test@mail.ru" class="linkification-ext" title="Linkification: mailto:test@mail.ru">test@mail.ru</a>'); $this->assertFalse($user->insertUser($user_data)); } function testInsertEmptyEmail() { $user = &new User(); $user_data = array('nickname'=>'testUser1', 'gender'=>'male', 'country'=>'us', 'password'=>'new', 'birth_date'=>'2007-05-31', 'email'=>''); $this->assertFalse($user->insertUser($user_data)); } function testCorrectDataInsert() { $user = &new User(); $user_data = array('nickname'=>'testUser1', 'gender'=>'male', 'country'=>'us', 'password'=>'new', 'birth_date'=>'2007-05-31', 'email'=>'<a href="mailto:test@mail.ru" class="linkification-ext" title="Linkification: mailto:test@mail.ru">test@mail.ru</a>'); $this->assertFalse($user->insertUser($user_data)); } } |
Сэкономив место в статье, я поступил несовсем правильно добавив сразу три теста в класс. Идеологически правильно было бы делать все в таком порядке: добавляем один тест, затем пишем минимально функционал, необходимый для того чтобы пройти тест, потом опять добавляем тестовый случай и опять “подгоняем” под него функционал.
Осталось только разместить где-нибудь код создания экземпляра класса UserTests. Для наглядности я поступил следующим образом: в пакете TESTS_RUNNER в папке examples нашел папку cases и скопировал ее в папку tests. Дальше, нужно создать внутри cases, папку user, а в ней файл user_test.php с таким кодом:
1 2 3 | require_once('user/models/UserTests.php'); $userTest = &new UserTests(); |
И, наконец, создаем класс модели.
mysite/application/user/models/User.php
1 2 3 4 5 6 7 8 | class User extends Zend_Db_Table { protected $_name = 'User'; public function insertUser($data) { } } |
Теперь самое время запустить наши тесты.
вводим в браузере url: http://mysite/tests/runtests.php
И получаем в результате нечто вроде такого:
Т.е. мы можем запустить тесты по отдельности либо группой, оооочень удобно, особенно, когда количество тестов перевалит за один
Итак запускам нужный нам user_test и получаем красную полосу, что вполне ожидаемо, так как функционала пока и нет.

Добавляем в метод insertUser, такой код:
1 2 3 4 5 6 7 8 9 10 | $password = md5($data['password']); $birthdate = $data['birth_date']['year'] . '-' . $data['birth_date']['month'] . '-' . $data['birth_date']['day']; $register_date = date('Y-m-d H:i:s'); $data_insert = array('nickname'=>$data['nickname'], 'email'=>$data['email'], 'gender'=>$data['gender'], 'password'=>$password, 'country'=>$data['country'], 'birthdate'=>$birthdate, 'register_date'=>$register_date, ); $result = $this->insert($data_insert); return $result; |
Запустив тест опять получим красную полосу, потому как проверки на пустой никнейм и пустой email не будут пройдены.
Добавим эти проверки:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | $result = false; if ( !empty($data['nickname']) && !empty($data['email']) ) { $password = md5($data['password']); $birthdate = $data['birth_date']['year'] . '-' . $data['birth_date']['month'] . '-' . $data['birth_date']['day']; $register_date = date('Y-m-d H:i:s'); $data_insert = array('nickname'=>$data['nickname'], 'email'=>$data['email'], 'gender'=>$data['gender'], 'password'=>$password, 'country'=>$data['country'], 'birthdate'=>$birthdate, 'register_date'=>$register_date, ); $result = $this->insert($data_insert); } return $result; |
При запуске тестов получим долгожданную зеленую полосу.

Комментарии
7 Комментариев к “Тестируем приложения на базе Zend Framework”
Практическое tdd на пальцах. Как раз для меня. Спасибо.
Я не ошибаюсь, что ранее эта статья была написана таким образом, что использовался Zend Test Runner? Если это действительно так, то почему состоялся переход на Limb Test Runner. Мне также хотелось бы разместить ссылку на эту статью с вики Limb Test Runner, что скажете?
2Серега:
К моему стыду, я не знаю что такое Zend Test Runner
Писал сразу для пакета Limb Test Runner, так как это первое решение которое я нашел когда начал писать тесты.
Ссылку на статью можете размещать, я только ЗА.
Доброго дня.
Возможно руки кривые тогда подскажите как лечить. Проделал указаные операции при запуске всегда отрабатывает все положительно при этом не выполняя ни одного теста из UserTest.php. Начал разбираться с примером из папочки cases при переносе кода из файла
mysite/test/application/user/models/UserTest.php
в
mysite/test/cases/user/user_test.php
все отрабатывает. Собственно вопрос чего так, что не так сделал. А то чет не дохожу своим умом.
И ещё подскажи на каком этапе ты делаеш инициализацию например подключения к базе при запуске тестов.
2 Александр: На выходных постараюсь ответить. Но лучше, если ты упакуешь и пришлешь свой код.
Спасибо, отличный пост.
Даа… Пока это у нас не очень сильно развито, так что придётся подождать.