Skip to content

Meta Fixtures Manager [MFM]

uglide edited this page Dec 30, 2012 · 1 revision

Meta Fixture Manager (MFM)

Meta Fixtures Manager - создан с целью упростить управление фикстурами БД и подготовку тестового окружения для выполнения тестов.

Проблематика

Для лучшего понимания происходящего, рассмотрим следующий тривиальный пример:

Предположим, что в нашей системе разработан некий модуль shops, функционал которого покрыт различными тестами (модульными и функциональными):

Допустим, что наша текущая задача - добавить тест для этого модуля, который нуждается в фикстуре магазина добавленной в БД. Смотря на файловую структуру тестов для shops модуля, мы можем сделать предположение, что фикстура магазина и код, который добавляет ее в БД уже есть в каком-то из тестов.

Проблема № 1 - Необходимо просмотреть все тесты данного модуля, для того, чтобы определить есть ли необходимый код для подготовки тестового окружения или нет.
Проблема № 2 - Проблема №1 влечет за собой дублирование кода в тестах, что существенно увеличивает их хрупкость и делает более сложным добавление новых тестов.

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

public static function setUpShop() { $shop = $shops->createRow(self::$fixtures['shop']); $shop->save(); return $shop; }

Мы добавляем его вызов в тест, и должны не забыть удалить фикстуру из БД после окончания теста

Проблема № 3 - Необходимость "ручного" контроля очистки фикстур

Но в ходе написания тестов очень часто возникает необходимость подготовить несколько связанных сущностей в БД и тут возникает следующая проблема:

Проблема № 4 - Неявные зависимости между тест кейсами за счет перекрестного использования методов для подготовки БД

Решение

Использование Meta Fixtures Manager :)

Для начала необходимо создать класс - контейнер для фикстур:

Класс наследуется от Core_Tests_Fixtures_Container_Abstract: class Shops_Fixtures_Container extends Core_Tests_Fixtures_Container_Abstract { ... }

В данном классе необходимо определить методы для подготовки окружения, в данном случае это будет setUpShop()

`

class Shops_Fixtures_Container extends Core_Tests_Fixtures_Container_Abstract
{

 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 - это метод, который будет вызван автоматически для удаления поднятой фикстуры.

Как использовать в тестах:

`

// 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
            )
    );
}

` Использование фикстур из контейнера

Как вы уже догадываетесь из заголовка, в данном менеджере фикстур есть возможность использовать фикстуры непосредственно из контейнера.

Предположим, у нас есть следующий контейнер с фикстурами:

`

class Shops_Fixtures_Container extends 	Core_Tests_Fixtures_Container_Abstract
{

    /**
     * Фикстуры должны быть добавлены в статическую
     * переменную класса $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'];
    // ....
}

`

Но зачем это, если можно просто вызвать в тесте статическую переменную контейнера?!

Работая с фикстурами через менеджер у вас есть возможность получать динамически созданные фикстуры:

`

class Shops_Fixtures_Container extends Core_Tests_Fixtures_Container_Abstract
{
    /**
     * Статическая фикстура
     */
     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 раз на этапе создания метода, для подъема фикстуры
  • тесты не захламляются большим количеством кода для поднятия фикстур и за счет этого мы получаем более чистые и понятные тесты
  • фикстуры для БД каждого модуля собраны в одном месте и ими проще управлять
  • получение динамически созданных фикстур