uglide / db-fixture-manager
DbFixture manager for functional tests
Installs: 837
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 1
Forks: 0
Open Issues: 0
pkg:composer/uglide/db-fixture-manager
Requires
- php: >=5.3.0
- phpunit/phpunit: 3.7.*
This package is not auto-updated.
Last update: 2025-10-25 20:05:47 UTC
README
DbFixtureManager- создан с целью упростить управление фикстурами БД и подготовку тестового окружения для выполнения тестов.
Проблематика
Для лучшего понимания происходящего, рассмотрим следующий тривиальный пример:
Предположим, что в нашей системе разработан некий модуль shops, функционал которого покрыт различными тестами (модульными и функциональными):
Допустим, что наша текущая задача - добавить тест для этого модуля, который нуждается в фикстуре магазина добавленной в БД. Смотря на файловую структуру тестов для shops модуля, мы можем сделать предположение, что фикстура магазина и код, который добавляет ее в БД уже есть в каком-то из тестов.
Проблема № 1 - Необходимо просмотреть все тесты данного модуля, для того, чтобы определить есть ли необходимый код для подготовки тестового окружения или нет.
Проблема № 2 - Проблема №1 влечет за собой дублирование кода в тестах, что существенно увеличивает их хрупкость и делает более сложным добавление новых тестов.
После титанических усилий мы все таки находим некий код, который добавляет фикстуру магазина, например:
public static function setUpShop() { $shop = $shops->createRow(self::$fixtures['shop']); $shop->save(); return $shop; }
Мы добавляем его вызов в тест, и должны не забыть удалить фикстуру из БД после окончания теста
Проблема № 3 - Необходимость "ручного" контроля очистки фикстур
Но в ходе написания тестов очень часто возникает необходимость подготовить несколько связанных сущностей в БД и тут возникает следующая проблема:
Проблема № 4 - Неявные зависимости между тест кейсами за счет перекрестного использования методов для подготовки БД
Решение
Использование DbFixtureManager :)
Для начала необходимо создать класс - контейнер для фикстур:
Класс наследуется от uglide\DbFixtureManager\ContainerAbstract:
use uglide\DbFixtureManager\ContainerAbstract;
class Shops_Fixtures_Container extends ContainerAbstract
{
    ...
}
В данном классе необходимо определить методы для подготовки окружения, в данном случае это будет setUpShop()
  use uglide\DbFixtureManager\ContainerAbstract;
  class Shops_Fixtures_Container extends ContainerAbstract
	{
   	 public static $fixtures = array(
	        'shop' =array(
	            'id' =1,
	            'alias' ='testshop',
	            'name' ='Test Shop',
	            'country_id'    = 1,
	            'state_id'  = 25,
	            'city_id'   = 28
	        )
	    );
	
	    /**
	     * @param array $config
	     *
	     * @return array
	     */
	
	    public function setUpShop($config = array('resetTable' =true))
	    {
	 
	        $shops = new Shops_Model_Shop_Table();
	        $shops->getDefaultAdapter()->query('SET FOREIGN_KEY_CHECKS=0;');
	        if (isset($config['resetTable']) && $config['resetTable']) {
	            $shops->delete(' true = true ');
	        }
	 
	        $shop = $shops->createRow(self::$fixtures['shop']);
	        $shop->save();
	 
	        /**
	         * Register cleaner
	         */
	        $this->_registerCleaner('_tearDownShop');
	        return $fixture;
	    }
	 
	    protected function _tearDownShop()
	    {
	        $shops = new Shops_Model_Shop_Table();
	        $shops->getDefaultAdapter()->query('SET FOREIGN_KEY_CHECKS=0;');
	        $shops->delete(' true = true ');
	    }
}
! Метод, который поднимает фикстуру обязательно должен регистрировать сборщик мусора (cleaner). Cleaner - это метод, который будет вызван автоматически для удаления поднятой фикстуры.
Также необходимо определить загрузчик контейнеров фикстур, например его можно определить в bootstrap.php:
/**
 * Init Fixtures loader
 */
spl_autoload_register(function($className) {
        $matches = array();
        if (preg_match('/([a-z]+)_Fixtures_Container/i', $className, $matches)) {
            $path = __DIR__ . '/fixtures/' . $matches[1] . '.php';
            if (file_exists($path)) {
                require_once $path;
            }
        }
    });
Далее необходимо унаследовать абстрактный класс тест кейса, и реализовать метод получения объекта для работы с Бд
use uglide\DbFixtureManager\TestCase;
class BaseTestCase extends TestCase
{
    protected function getDb()
    {
        return Database::instance();
    }
}
Все тест-кейсы с фикстурами будут наследоваться от этого класса
Как использовать в тестах:
// Example 1 
    /**
     * Самый простой случай - нам необходимо добавить в БД фикстуру,
     * которая не используется после этого в тесте
     * @fixture Shops::setUpShop
     */
    public function testIndexAction()
    {
        // код теста
    }
// Example 2 
    /**
     * Добавляем в БД фикстуру,
     * и результат выполнения метода setUpShop будем использовать в тесте
     * @fixture $shop Shops::setUpShop
     */
    public function testIndexAction()
    {
        // ....
        $this->_fixture['shop']; // фикстура доступна по указанному имени в массиве $this->_fixtures
        // ....
    }
// Example 3 
    /**
     * Добавляем в БД фикстуру, с помощью метода setUpShop,
     * которому нужно передать определенные параметры
     * знак "+" после названия метода обозначает,
     * что в данный метод будет передана фикстура
     * из дата-провайдера в качестве аргумента
     * @fixture $shop Shops::setUpShop+
     * @dataProvider shopDataProvider
     */
    public function testIndexAction($fixtureSettings) // фикстура передаваемая дата-провайдером доступна как аргумент теста
    {
        // ....
        $this->_fixture['shop']; // фикстура доступна по указанному имени в массиве $this->_fixtures
        // ....
    }
     
    /**
    * Стандартный дата-провайдер PHPUnit
    */
    public function shopDataProvider()
    {
        return array('validTest' => array('resetTable' => false));
    }
     
// Example 4 
    /**
     * Добавляем в БД несколько фикстур,
     * @fixture $shop Shops::setUpShop
     * @fixture $user Users::setUpUser
     */
    public function testIndexAction()
    {
        // ....
        $this->_fixture['shop'];
        $this->_fixture['user'];
        // ....
    }    
//Example 5 
    /**
     * Добавляем в БД несколько фикстур,
     * с помощью нескольких методов.
     * Обратите внимание на то, что методы
     * получают различные данные из дата провайдера
     * @fixture $shop Shops::setUpShop
     * @fixture $user Users::setUpUser
     * @dataProvider shopDataProvider
     */
    public function testIndexAction()
    {
        // ....
        $this->_fixture['shop'];
        $this->_fixture['user'];
        // ....
    }    
 
    /**
    * Стандартный дата-провайдер PHPUnit
    */
    public function shopDataProvider()
    {
        return array(
            'validTest' =>
                array(
                    'setUpShop' => array('resetTable' => false), //эти данные будут переданы в Shops::setUpShop
                    'setUpUser' => array('id' => 100), // а эти в Users::setUpUser
                )
        );
    }
Использование фикстур из контейнера
Как вы уже догадываетесь из заголовка, в данном менеджере фикстур есть возможность использовать фикстуры непосредственно из контейнера.
Предположим, у нас есть следующий контейнер с фикстурами:
        use uglide\DbFixtureManager\ContainerAbstract;
        class Shops_Fixtures_Container extends ContainerAbstract
	{
	    /**
	     * Фикстуры должны быть добавлены в статическую
	     * переменную класса $fixtures
	     */
	     public static $fixtures = array(
	        'shop' => array(
	            'id' => 1,
	            'alias' => 'testshop',
	            'name' => 'Test Shop',
	            'country_id'    =>  1,
	            'state_id'  =>  25,
	            'city_id'   =>  28
	        )
	    );
	 }
Данную фикстуру мы можем использовать в тестах следующим образом:
	//  Example 1
    /**
     * Указываем имя переменной,
     * в которую будет записана фикстура
     * и имя фикстуры со знаком $
     * @fixture $superShop Shops::$shop 
     */
    public function testIndexAction()
    {
        // ....
        // теперь мы можем использовать в тесте
        // фикстуру
        $this->_fixture['superShop'];
        // ....
    } 
 
	//  Example 2 
    /**
     * Если имя целевой переменной не указано явно,
     * то фикстура будет доступна в массиве
     * $this->_fixture по своему индексу
     * из контейнера
     * @fixture Shops::$shop    
     */
    public function testIndexAction()
    {
        // ....
        // теперь мы можем использовать в тесте
        // фикстуру
        $this->_fixture['shop'];
        // ....
    }
Но зачем это, если можно просто вызвать в тесте статическую переменную контейнера?!
Работая с фикстурами через менеджер у вас есть возможность получать динамически созданные фикстуры:
        use uglide\DbFixtureManager\ContainerAbstract;
        class Shops_Fixtures_Container extends ContainerAbstract
	{
	    /**
	     * Статическая фикстура
	     */
	     public static $fixtures = array(
	        'shop' => array(
	            'id' => 1,
	            'alias' => 'testshop',
	            'name' => 'Test Shop',
	            'country_id'    =>  1,
	            'state_id'  =>  25,
	            'city_id'   =>  28
	        )
	    );
	 
	    public function __construct(Zend_Db_Adapter_Abstract $dbAdapter)
	    {
	        parent::__construct($dbAdapter);
	 
	        // генерируем фикстуру 
	        self::$fixtures['someGeneratedFixture'] = $this->someShopFixtureGenerator();
	    }
	 
	    // .......
	 }
	
	/**
	     * Мы можем получить динамически созданную
	     * фикстуру в тесте
	     * @fixture $someGeneratedFixture Shops::$someGeneratedFixture
	     */
	    public function testIndexAction()
	    {
	        // ....
	        $this->_fixture['someGeneratedFixture'];
	        // ....
	    }
Преимущества
- автоматическое удаление фикстур, об организации которого нужно побеспокоиться только 1 раз на этапе создания метода, для подъема фикстуры
- тесты не захламляются большим количеством кода для поднятия фикстур и за счет этого мы получаем более чистые и понятные тесты
- фикстуры для БД каждого модуля собраны в одном месте и ими проще управлять
- получение динамически созданных фикстур