我正在测试一个只检索新闻系统所有“帖子”的工厂。我会将示例简化为尽可能简单的事情:
$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 |
+---------+---------------------+-------------+
我想测试那种行为;现在,我们只关注第一个:
所以我制作了一个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");
}
}
我的主要问题是如何设置该测试?
详细说明,我试着理解:
sqlite::memory:
的例子,用sqlite测试基于MySQL的查询是个好主意吗?我可以使用mysql::memory:
吗?dbData.xml
恢复所有数据?getConnection()
和getDataSet()
?感谢阅读&amp;分享你的知识!
答案 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);
}
}
希望能帮助需要一些额外解释的其他人。 如果您有任何意见,建议或想法,欢迎您提出意见,因为我认为此解决方案不是一个完全有效的解决方案。 (设置缓慢而且需要很长时间,需要双重连接。)