Denis.in.ua

Блог имени Меня

Тестируем приложения на базе Zend Framework

7 comments

Zend framework plus simpletestДумаю мало кого из разработчиков можно удивить терминами «модульные тесты» и «разработка через тестирование». Не буду здесь читать проповедь о пользе модульного тестирования и вреде нетестирования. Статья посвящена настройке среды для тестирования приложений на базе 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

И получаем в результате нечто вроде такого:group tests

Т.е. мы можем запустить тесты по отдельности либо группой, оооочень удобно, особенно, когда количество тестов перевалит за один :)

Итак запускам нужный нам user_test и получаем красную полосу, что вполне ожидаемо, так как функционала пока и нет.

red stripe in testing case

Добавляем в метод 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'=&gt;$data['nickname'], 'email'=&gt;$data['email'], 'gender'=&gt;$data['gender'],
'password'=>$password, 'country'=>$data['country'], 'birthdate'=>$birthdate,
'register_date'=>$register_date,
);
$result = $this->insert($data_insert);
}
return $result;

При запуске тестов получим долгожданную зеленую полосу.

famous green stripe in testing

Written by Денис Солошенко

Июнь 25th, 2007 at 1:55 дп

Posted in PHP,Zend Framework