2011-03-23 135 views
2

我正在测试一个工厂,它只是检索新闻系统的所有“帖子”。我就砍例子来尽可能简单的东西: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> 

好了,所以让我们只检查第一个测试(不返回从该news_id#2 XML数据集)

我必须扩展类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"); 
    } 
} 

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

在细节,我试着去理解:

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

感谢您的阅读&分享您的知识!

回答

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

0

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

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

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

+0

我知道这件事,我没有贴整个文件(我也编辑了列的数量......)你对你的答案是正确的。不知怎的,就像我写的第一个版本的测试。但现在我真的需要测试查询。问题不在于测试结果,更重要的是找到一种方便的方式来提供可供真实查询执行使用的测试数据,并替换默认的PDO实例。我会编辑我的问题,以反映...感谢您的意见! – FMaz008 2011-03-23 21:42:26

0

到目前为止,我已经了解到: 我应该创建一个testdb数据库还是所有模拟/虚拟?

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

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

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

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

有getSetUpOperation和getTearDownOperation,它们像setup和tearDown方法一样工作。加入这将截断在DataSet mentionned表,并重新插入该XML文件的所有数据:我应该在哪里调用的getConnection()和getDataSet()

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

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

另外,我发现,我们需要创建表结构(数据集不处理),所以我完全--slow--工作代码为:

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

} 

希望这将有助于其他需要一些额外解释的人。 如果您有任何意见,建议或意见,欢迎您的意见,因为我不认为这个解决方案是一个完全有效的解决方案。 (缓慢而长期设置,并且需要一个双连接)。