RSS

Структура каталогов

   

Консультация юриста
Ваш регион:
Ваше имя:

Ваш телефон

(можно сотовый)

Ваш вопрос (можно кратко)


 
Учебник РНР
Назад

Структура каталогов


Структура каталогов, описанная в первой статье, дополняется каталогом models, в котором будут располагаться файлы с тестами. В этом каталоге будет подкаталог fixtures, в нем я буду хранить данные для тестирования. Причем общие для всех тестов данные будут помещены непосредственно в этот каталог, а данные, специфичные для тестируемых классов будут располагаться в одноименных каталогах.

Cтруктура каталогов

project
|--lib
|
|--application
|--tests
|
|--application
| |--bootstrap.php
| |--ControllerTestCase.php
| |--DbTestCase.php
| |--XmlDataSet.php
| |
| |--models
| |--CategoryTest.php
| |--fixtures
| |--init.xml
| |--Model_DbTable_Category
| |--addCategory.xml
| |--delBeginCategory.xml
| |--delEndCategory.xml
| |--getCategory.xml
| |--updateBeginCategory.xml
| |--updateEndCategory.xml
|
|--phpunit.xml

В этом примере я рассмотрю тестирование модели таблицы tcategory. Напишу тесты для функций создания, изменения, удаления, получения категории.

Инфраструктурные файлы

Перейдем к рассмотрению файлов.
Файл bootstrap.php не претерпел принципиальных изменений, лишь добавлено подключение нескольких новых файлов.

Файл bootstrap.php

<?php 
error_reporting( E_ALL | E_STRICT );

date_default_timezone_set('Europe/Moscow');

define('BASE_PATH', realpath(dirname(__FILE__) . '/../../'));
define('APPLICATION_PATH', BASE_PATH . '/application');
define('CONFIG_PATH', APPLICATION_PATH . '/configs/application.ini');

set_include_path(
'.'
. PATH_SEPARATOR . BASE_PATH . '/library'
. PATH_SEPARATOR . get_include_path()
);

define('APPLICATION_ENV', 'testing');

require_once 'Zend/Application.php';
require_once 'ControllerTestCase.php';
require_once 'DbTestCase.php';
require_once 'XmlDataSet.php';

Файл phpunit.xml не изменился совсем.
По аналогии с ControllerTestCase создаю класс DbTestCase, от которого будут наследоваться все тесты.

Файл DbTestCase.php

<?php

require_once 'Zend/Test/PHPUnit/DatabaseTestCase.php';

abstract class DbTestCase extends Zend_Test_PHPUnit_DatabaseTestCase
{
protected $_db;
protected $_model;
protected $_modelClass;

protected $_fixturesDir;
protected $_filesDir;
protected $_initDataSet;

public function setUp()
{
$this->_fixturesDir = dirname(__FILE__).'/models/fixtures/';
$this->_filesDir = $this->_fixturesDir.$this->_modelClass.'/';
$this->_model = new $this->_modelClass($this->getAdapter());
parent::setUp();
}

protected function getTearDownOperation()
{
return PHPUnit_Extensions_Database_Operation_Factory::DELETE_ALL();
}

protected function getConnection()
{
if (empty($this->_db))
{
$vApplication = new Zend_Application(APPLICATION_ENV,CONFIG_PATH);
$vApplication->bootstrap();
$vOptions = $vApplication->getOptions();

$vConfig = new Zend_Config_Ini(CONFIG_PATH,'testing');
$vDbname = $vConfig->resources->db->params->dbname;

$vDb = $vApplication->getBootstrap()->getPluginResource('db')->getDbAdapter();
$this->_db = $this->createZendDbConnection($vDb, $vDbname);
}
return $this->_db;
}

protected function getDataSet($pFileName=null)
{
if ($pFileName===null)
{
$vFileName = $this->_fixturesDir.'init.xml';
}
else
{
$vFileName = $pFileName;
}
return $this->createXmlDataSet($vFileName);
}

protected function prepareInitData($pInitData)
{
$this->getDatabaseTester()->setDataSet($this->getDataSet($pInitData));
$this->getDatabaseTester()->onSetUp();
}
}

Рассмотрим файл более внимательно.
Функция setUp() запускается перед началом каждого теста PHPUnit. Часть 04 Тестовые окружения (Fixtures)).
Ее предназначение подготовить тестовое окружение к работе: проинициализировать переменные, хранящие пути к файлам, и загрузить из xml-файла тестовые данные. Функция getTearDownOperation() определяет операцию, которая будет выполняться после каждого теста PHPUnit. Часть 07 Тестирование базы данных. В данном случае будет выполняться очистка таблиц.
Таким образом, с помощью функций setUp() и getTearDownOperation() для каждого теста создается специальное тестового окружение, которое по завершении работы удаляется. Получается, что тесты работают со своими персональными данными и не влияют друг на друга.

Функция getConnection() устанавливает соединение с базой данных и не требует особых пояснений. Отмечу лишь то, что для тестирования создана специальная база, по структуре полностью повторяющая боевую, имя базы зачитывается из ini-файла, это типовой конфигурационный файл Zend Framework.

Функция protected function getDataSet($pFileName=null) - это реализация обязательно метода класса Zend_Test_PHPUnit_DatabaseTestCase, назначение метода - создать тестовые данные. Для всех тестов предполагается один файл с данными init.xml, однако тест может использовать и свой специфический файл.

Класс Zend_Test_PHPUnit_DatabaseTestCase - это Zend Framework заточенный наследник PHPUnit_Extensions_Database_TestCase.

Функция protected function prepareInitData($pInitData) выполняют установку первоначальных данные, если тесту требуются специальные условия.

Файл init.xml
В этом файле хранится конфигурация среды тестирования. Если тест не использует свой конфигурационный файл, то по умолчанию берется этот.

<?xml version="1.0" encoding="UTF-8"?>

<dataset>
<table name="tcategory">

<column>Id</column>
<column>Name</column>
<column>ParentId</column>
<column>Description</column>
<column>OrderNo</column>
<column>Level</column>
<column>FlagHasChildren</column>

</table>

</dataset>

Как видите, это простой xml-файл, который описывает структуру данных и собственно сами данные, более подробно о конфигурационных файлах написано.
Данные не заданы, поэтому после загрузки этого файла из таблицы tcategory будут удалены все записи.

Класс тестирования

Переходим к тестам.

Файл CategoryTest.php
В этом файле находится основной класс.

<?php
require_once APPLICATION_PATH.'/models/DbTable/Category.php';

class CategoryTest extends DbTestCase
{
protected $_TableName = 'tcategory';

public function __construct()
{
$this->_modelClass = 'Model_DbTable_Category';
}

public function testaddCategory()
{
$vDataSet = new XmlDataSet($this->_filesDir.'addCategory.xml');

$this->_model->addCategory(
$vDataSet->getValue($this->_TableName,0,"Name"),
$vDataSet->getValue($this->_TableName,0,"ParentId"),
$vDataSet->getValue($this->_TableName,0,"Description"));

$vExpected = $this->createXmlDataSet($this->_filesDir.'addCategory.xml');
$vActual = new PHPUnit_Extensions_Database_DataSet_QueryDataSet($this->getConnection());
$vActual->addTable($this->_TableName);
$this->assertDataSetsEqual($vExpected, $vActual);
}

public function testgetCategory()
{
$this->prepareInitData($this->_filesDir.'getCategory.xml');
$vExpected = new XmlDataSet($this->_filesDir.'getCategory.xml');

$vActual = $this->_model->getCategory($vExpected->getValue($this->_TableName,0,"Id"));

$this->assertEquals($vActual["Id"],
$vExpected->getValue($this->_TableName,0,"Id"));
$this->assertEquals($vActual["Name"],
$vExpected->getValue($this->_TableName,0,"Name"));
$this->assertEquals($vActual["ParentId"],
$vExpected->getValue($this->_TableName,0,"ParentId"));
$this->assertEquals($vActual["Description"],
$vExpected->getValue($this->_TableName,0,"Description"));
}

public function testupdateCategory()
{
$this->prepareInitData($this->_filesDir.'updateBeginCategory.xml');

$vExpected = new XmlDataSet($this->_filesDir.'updateEndCategory.xml');

$this->_model->updateCategory(
$vExpected->getValue($this->_TableName,0,"Id"),
$vExpected->getValue($this->_TableName,0,"Name"),
$vExpected->getValue($this->_TableName,0,"ParentId"),
$vExpected->getValue($this->_TableName,0,"Description"));

$vActual = new PHPUnit_Extensions_Database_DataSet_QueryDataSet($this->getConnection());
$vActual->addTable($this->_TableName);
$this->assertDataSetsEqual($this->createXmlDataSet($this->_filesDir.'updateEndCategory.xml'),
$vActual);
}

public function testdelCategory()
{
$this->prepareInitData($this->_filesDir.'delBeginCategory.xml');
$vExpected = new XmlDataSet($this->_filesDir.'delBeginCategory.xml');

$this->_model->delCategory($vExpected->getValue($this->_TableName,0,"Id"));

$vActual = new PHPUnit_Extensions_Database_DataSet_QueryDataSet($this->getConnection());
$vActual->addTable($this->_TableName);
$this->assertDataSetsEqual($this->createXmlDataSet($this->_filesDir.'delEndCategory.xml'),
$vActual);
}
}

Обратите внимание на наименования тестовых методов. Наименование образуется так: test+тестируемый метод.
Тестируем класс Model_DbTable_Category, поэтому разумно все конфигурационные файлы поместить в каталог fixtures/Model_DbTable_Category.
Для функций создания и получения категории используется типовой набор первоначальных данных init.xml. А вот для функций удаления и изменения яребуются специальные стартовые условия. 

Функция testaddCategory()

Это тест функции создания категории. Смысл функции заключается в создании категории с заранее заданными параметрами, после чего состояние базы, т.е. тестируемой таблицы сравнивается с заранее известным. Если состояния совпали, значит функция отработала как надо.
Для обращения к параметрам XML-файла я использую свой класс XmlDataSet, вот его код.

Файл XmlDataSet.php

<?php

require_once 'PHPUnit/Extensions/Database/DataSet/XmlDataSet.php';

class XmlDataSet extends PHPUnit_Extensions_Database_DataSet_XmlDataSet
{
private $_DataSet;

public function __construct($pXmlFile)
{
$this->_DataSet = new PHPUnit_Extensions_Database_DataSet_XmlDataSet($pXmlFile);
}

public function getValue($pTableName, $pRowIndex, $pColumnName)
{
$vTableColumns = array();
$vTableValues = array();
$this->_DataSet->getTableInfo($vTableColumns, $vTableValues);

return $vTableValues[$pTableName][$pRowIndex][$pColumnName];
}
}

Подозреваю, что есть и более правильный способ, если знаете - подскажите.

Класс XmlDataSet унаследован от PHPUnit_Extensions_Database_DataSet_XmlDataSet и нужен чтобы исправить непонятную особенность - недоступность метода getTableInfo. Функция getValue нужна для упрощения извлечения данных из xml-файлов.

  • А теперь как работает тест:
  • PHPUnit запускает функцию setUp(), переопределенную в DbTestCase, эта функция из файла init.xml зачитывает инициализационные данные для таблицы, т.е. по сути очищает эту таблицу.
  • Запускается сам тест testaddCategory, тест создает категорию и проверяет, что получилось в результате.
  • После завершения теста, независимо от результата выполняется функция tearDown(), в DbTestCase я не стал ее переопределять как setUp(),т.к. в этом нет смысла. tearDown() вызывает функцию getTearDownOperation(), которая переопределена в DbTestCase, эта функция очищает все результаты работы теста.

Таким образом, после выполнения testaddCategory все готово для выполнения других тестов, т.к. база данных находится в первоначальном состоянии.
Изолированность тестов имеет множество плюсов: результат не зависит от последовательности выполнения, можно смело менять тестовые данные любого теста и не бояться повлиять на выполнение других.

Принцип работы других тестов аналогичен тесту создания категории: создается среда тестирования, выполняется функция тестируемой модели, полученный результат сравнивается с эталоном, тестовая среда возвращается к первоначальному состоянию.

Конфигурационные файлы

Применение конфигурационных файлов позволяет вносить изменения в тесты, не исправляя исходные тексты этих тестов. Это делает возможным разделение работы между программистом - автором тестов и тестировщиком. Тестировщику, чтобы изменить данные, не надо лазить по исходным текстам.

XML файлы выбраны для хранения тестовых данных не случайно (о других возможностях. XML позволяет в очень удобном виде описывать структуры данных и сами данные.

Файл addCategory.xml
Эталонные данные для тестирования создания категории.

<?xml version="1.0" encoding="UTF-8"?>

<dataset>
<table name="tcategory">

<column>Id</column>
<column>Name</column>
<column>ParentId</column>
<column>Description</column>
<column>OrderNo</column>
<column>Level</column>
<column>FlagHasChildren</column>

<row>
<value>1</value>
<value>addCategory</value>
<null/>
<value>add Category</value>
<null/>
<null/>
<null/>
</row>

</table>

</dataset>

Файл delBeginCategory.xml
Первоначальные данные для тестирования удаления категории.

<?xml version="1.0" encoding="UTF-8"?>

<dataset>
<table name="tcategory">

<column>Id</column>
<column>Name</column>
<column>ParentId</column>
<column>Description</column>
<column>OrderNo</column>
<column>Level</column>
<column>FlagHasChildren</column>

<row>
<value>1</value>
<value>delCategory</value>
<null/>
<value>del Category</value>
<null/>
<null/>
<null/>
</row>

<row>
<value>2</value>
<value>CategoryAfterDel</value>
<null/>
<value>Category After Del</value>
<null/>
<null/>
<null/>
</row>

</table>

</dataset>

Файл delEndCategory.xml
Эталонные данные для тестирования удаления категории. Как видите, категория с ID=1 должна быть удалена.

<?xml version="1.0" encoding="UTF-8"?>

<dataset>
<table name="tcategory">

<column>Id</column>
<column>Name</column>
<column>ParentId</column>
<column>Description</column>
<column>OrderNo</column>
<column>Level</column>
<column>FlagHasChildren</column>

<row>
<value>2</value>
<value>CategoryAfterDel</value>
<null/>
<value>Category After Del</value>
<null/>
<null/>
<null/>
</row>

</table>

</dataset>

Файл getCategory.xml
Эталонные данные для тестирования функции получения категории.

<?xml version="1.0" encoding="UTF-8"?>

<dataset>
<table name="tcategory">

<column>Id</column>
<column>Name</column>
<column>ParentId</column>
<column>Description</column>
<column>OrderNo</column>
<column>Level</column>
<column>FlagHasChildren</column>

<row>
<value>1</value>
<value>getCategory</value>
<null/>
<value>get Category</value>
<null/>
<null/>
<null/>
</row>

</table>

</dataset>

Файл updateBeginCategory.xml
Первоначальные данные для тестирования изменения категории.

<?xml version="1.0" encoding="UTF-8"?>

<dataset>
<table name="tcategory">

<column>Id</column>
<column>Name</column>
<column>ParentId</column>
<column>Description</column>
<column>OrderNo</column>
<column>Level</column>
<column>FlagHasChildren</column>

<row>
<value>1</value>
<value>updateCategory</value>
<null/>
<value>update Category</value>
<null/>
<null/>
<null/>
</row>

</table>

</dataset>

Файл updateEndCategory.xml
Эталонные данные для тестирования изменения категории.

<?xml version="1.0" encoding="UTF-8"?>

<dataset>
<table name="tcategory">

<column>Id</column>
<column>Name</column>
<column>ParentId</column>
<column>Description</column>
<column>OrderNo</column>
<column>Level</column>
<column>FlagHasChildren</column>

<row>
<value>1</value>
<value>updated</value>
<null/>
<value>updated successfully</value>
<null/>
<null/>
<null/>
</row>

</table>

</dataset>
Назад Оглавление  
Функции. Индекс. Вверх  

po gonn © 2005 "JULI'S BEEHIVE"