PHPUnit - 断言查询从数据库返回好的数据

时间:2011-03-23 20:53:49

标签: database phpunit

我正在测试一个只检索新闻系统所有“帖子”的工厂。我会将示例简化为尽可能简单的事情:

$newsFactory->getAllNews();

表格如下:

+---------+---------------------+-------------+
| news_id | news_publishedDate  | news_active |
+---------+---------------------+-------------+
|       1 | 2010-03-22 13:20:22 |           1 |
|       2 | 2010-03-23 13:20:22 |           1 |
|      14 | 2010-03-23 13:20:22 |           0 |
|      15 | 2010-03-23 13:20:22 |           1 |
+---------+---------------------+-------------+

我想测试那种行为;现在,我们只关注第一个:

  • 确保查询仅返回news_active = 1
  • 确保查询将返回news_publishedDate排序的元素,从最新到旧版本。

所以我制作了一个dbData.xml数据集,我认为它是一个很好的测试数据:

<?xml version="1.0" encoding="UTF-8" ?>
<dataset>
  <table name="news">
    <column>news_id</column>
    <column>news_publishedDate</column>
    <column>news_active</column>
    <row>
        <value>1</value>
        <value>2010-03-20 08:55:05</value>
        <value>1</value>
    </row>
    <row>
        <value>2</value>
        <value>2010-03-20 08:55:05</value>
        <value>0</value>
    </row>
    <row>
        <value>3</value>
        <value>2011-03-20 08:55:05</value>
        <value>1</value>
    </row>
  </table>
</dataset>

好的,我们只需检查第一个测试(不从XML数据集返回news_id#2)

我必须扩展PHPUnit_Extensions_Database_TestCase类才能生成我的NewsFactoryTest类:

<?php
require_once 'PHPUnit/Extensions/Database/TestCase.php';

class NewsFactoryTest extends PHPUnit_Extensions_Database_TestCase
{
    protected $db;


    protected function getConnection()
    {
        $this->db = new PDO('mysql:host=localhost;dbname=testdb', 'root', '');
        return $this->createDefaultDBConnection($this->db, 'testdb');
    }

    protected function getDataSet()
    {
        return $this->createXMLDataSet(dir(__FILE__) . DIRECTORY_SEPARATOR . 'dbData.xml');
    }

    public function testGetNewsById()
    {
        $newsFactory = new NewsFactory($this->db);
        $news = $newsFactory->getNewsById();
        // ???
        $this->assertEquals(2, count($news), "Should return only 2 results");
    }
}

我的主要问题是如何设置该测试?

详细说明,我试着理解:

  • 我应该创建一个testdb数据库还是所有模拟/虚拟数据库?
    • 我见过很多使用sqlite::memory:的例子,用sqlite测试基于MySQL的查询是个好主意吗?我可以使用mysql::memory:吗?
    • 如果它是真正的数据库,如何在每次测试运行之前从数据库中的dbData.xml恢复所有数据?
  • 我应该在哪里拨打getConnection()getDataSet()

感谢阅读&amp;分享你的知识!

3 个答案:

答案 0 :(得分:3)

我在我们的项目中设置了数据库测试,这里有一些对我们有用的答案和课程:

  

我应该创建一个testdb数据库还是全部模拟/虚拟?

我在开始时问了同样的问题,我们都知道它确实是一个真正的数据库运行。

  

我见过很多使用sqlite :: memory的例子:用sqlite测试基于MySQL的查询是个好主意吗?我可以使用mysql :: memory代替吗?

我尝试使用sqlite来提高性能,但发现SQL不同,不能在我们现有代码的每个地方使用。我能够将MySQL MEMORY引擎用于大多数考虑的表(对于某些表,例如BLOB列,不可能)。

  

如果它是真正的数据库,如何在每次测试运行之前从数据库中的dbData.xml恢复所有数据?

我编写了一个脚本来调用模式的mysqldump及其远程测试服务器中的所有表,将它们插入本地服务器,并将所有可能的表引擎转换为MEMORY。这确实需要时间,但由于模式在测试之间不会发生变化,因此它只在最顶层的TestSuite上运行一次,或者根据开发人员在本地系统上的需要单独运行。

数据集在每次测试开始时加载,由于表已经存在并且在内存中,因此测试之间的插入和截断很快。

  
    

我应该在哪里调用getConnection()和getDataSet()?

  

我们已经有一个扩展TestCase的辅助类,所以我无法使用PHPUnit_Extensions_Database_TestCase。我将设置函数添加到该助手类中,从未调用或必须实现getDataSet()。我确实使用getConnection()从断言函数中的修改数据创建数据集。

/**
 * @param PHPUnit_Extensions_Database_DataSet_IDataSet $expected_data_fixture
 * @param string|array $tables
 */
protected function assertDataFixturesEqual($expected_data_fixture, $tables){
    if(!is_array($tables)){
        $tables = array($tables);
    }
    PHPUnit_Extensions_Database_TestCase::assertDataSetsEqual($expected_data_fixture, $this->DbTester->getConnection()->createDataSet($tables));
}

编辑: 我找到了一些我用过的资源书签,因为PHPUnit文档有点缺乏:

http://www.ds-o.com/archives/63-PHPUnit-Database-Extension-DBUnit-Port.html

http://www.ds-o.com/archives/64-Adding-Database-Tests-to-Existing-PHPUnit-Test-Cases.html

答案 1 :(得分:0)

我没有使用PHPUnit的数据库测试用例,所以我必须将我的答案限制在断言中。您可以声明$news中不存在ID 2,或者您可以断言$news中的每个对象都处于非活动状态。后者更灵活,因为在向测试数据集添加数据时不需要更改测试。

$news = $newsFactory->getNewsById();
foreach ($news as $item) {
    self::assertTrue($news->isActive());
}

顺便说一句,数据集中发布的日期都是相同的。这将使测试订单变得不可能。 ;)

答案 2 :(得分:0)

到目前为止,我已经明白: 我应该创建一个testdb数据库还是全部模拟/虚拟?

它创建了一个真正的数据库,并且使用getSetUpOperation方法,因为每个测试都会截断并重新导入表格,所以它非常慢,即使对于少量数据,也要求很多硬盘驱动器。 (〜1秒/测试)

我见过很多使用sqlite :: memory的例子:用sqlite测试基于MySQL的查询是个好主意吗?我可以使用mysql :: memory代替吗?

我还是不知道。我认为现在真的可以使用MySQL。

如果它是真正的数据库,如何在每次测试运行之前从数据库中的dbData.xml恢复所有数据?

getSetUpOperation和getTearDownOperation就像setup和tearDown方法一样。添加它将截断dataSet中提到的表,并重新插入该xml文件的所有数据:

/**
 * Executed before each
 *
 * @return PHPUnit_Extensions_Database_Operation_DatabaseOperation
 */
protected function getSetUpOperation()
{
    return PHPUnit_Extensions_Database_Operation_Factory::CLEAN_INSERT();
}

我应该在哪里调用getConnection()和getDataSet()?

无处。这些是自动调用的魔法。在测试之前调用getConnection(有点像__construct,但我不确定顺序)并且在需要dataSet时将调用getDataSet。我认为在我的情况下,只有getSetUpOperation对dataSet有一个依赖...所以在后台它会在每次测试之前调用getDataSet方法来进行CLEAN_INSERT操作。

另外,我发现我们需要创建表结构(数据集不能处理),所以我的完整 - 低 - 工作代码是:

<?php
require_once 'PHPUnit/Extensions/Database/TestCase.php';

class NewsFactoryTest extends PHPUnit_Extensions_Database_TestCase
{
/**
 * Custom PDO instance required by the SUT.
 *
 * @var Core_Db_Driver_iConnector
 */
protected $db;

/**
 * Create a connexion.
 * Note: les constantes de connexion sont définit dans bootstrap.php.
 * 
 * @return PHPUnit_Extensions_Database_DB_IDatabaseConnection
 */
protected function getConnection()
{

    //Instanciate the connexion required by the system under test.
    $this->db = new Core_Db_Driver_PDO('mysql:host=' . TEST_DB_HOST . ';dbname=' . TEST_DB_BASE, TEST_DB_USER, TEST_DB_PASS, array());

    //Create a genuine PDO connexion, required for PHPUnit_Extensions_Database_TestCase.
    $db = new PDO('mysql:host=' . TEST_DB_HOST . ';dbname=' . TEST_DB_BASE, TEST_DB_USER, TEST_DB_PASS);
    $this->createTableSchema($db);
    return $this->createDefaultDBConnection($db, TEST_DB_BASE);
}

/**
 * Load the required table schemes.
 *
 * @param PDO $db
 * @return void
 */
protected function createTableSchema(PDO $db)
{
    $schemaPath = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'sql_schema' . DIRECTORY_SEPARATOR;
    $query = file_get_contents($schemaPath . 'news.sql');

    $db->exec($query);

    $query = file_get_contents($schemaPath . 'news_locale.sql');
    $db->exec($query);
}

/**
 * Load the dataSet in memory.
 *
 * @return PHPUnit_Extensions_Database_DataSet_IDataSet
 */
protected function getDataSet()
{
    return $this->createXMLDataSet(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'newsFactory_dataSet.xml');
}

/**
 * Method executed before each test
 *
 * @return PHPUnit_Extensions_Database_Operation_DatabaseOperation
 */
protected function getSetUpOperation()
{
    //TRUNCATE the table mentionned in the dataSet, then re-insert the content of the dataset.
    return PHPUnit_Extensions_Database_Operation_Factory::CLEAN_INSERT();
}

/**
 * Method executed after each test
 *
 * @return PHPUnit_Extensions_Database_Operation_DatabaseOperation
 */
protected function getTearDownOperation()
{
    //Do nothing ( yup, their's a code for that )
    return PHPUnit_Extensions_Database_Operation_Factory::NONE();
}


/**
 * @covers NewsFactory::getNewsById
 */
public function testGetNewsById()
{
    $newsFactory = new NewsFactory($this->db);
    $news = $newsFactory->getNewsById(999);
    $this->assertFalse($news);
}

}

希望能帮助需要一些额外解释的其他人。 如果您有任何意见,建议或想法,欢迎您提出意见,因为我认为此解决方案不是一个完全有效的解决方案。 (设置缓慢而且需要很长时间,需要双重连接。)