uglide/db-fixture-manager

DbFixture 管理器,用于功能测试

0.1 2013-04-10 13:26 UTC

This package is not auto-updated.

Last update: 2024-09-14 14:34:48 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'];
	        // ....
	    }

优点

  • 自动删除固定值,只需在创建方法时关注一次,即可在提供固定值时进行删除
  • 测试不会因为大量用于提供固定值的代码而变得复杂,因此我们得到更干净、更易于理解的测试
  • 每个模块的数据库固定值都集中在一个地方,更易于管理
  • 获取动态创建的固定值