# PHPUnit 数据库测试用例的配置
一般而言,使用 PHPUnit 时,测试用例都是按如下方式扩展自 `PHPUnit_Framework_TestCase` 类:
~~~
<?php
class MyTest extends PHPUnit_Framework_TestCase
{
public function testCalculate()
{
$this->assertEquals(2, 1 + 1);
}
}
?>
~~~
如果测试代码用到了数据库扩展模块,那么建立的过程就会更复杂一些,需要扩展另一个抽象 TestCase 类,它要求实现两个抽象方法,`getConnection()` 和 `getDataSet()`:
~~~
<?php
class MyGuestbookTest extends PHPUnit_Extensions_Database_TestCase
{
/**
* @return PHPUnit_Extensions_Database_DB_IDatabaseConnection
*/
public function getConnection()
{
$pdo = new PDO('sqlite::memory:');
return $this->createDefaultDBConnection($pdo, ':memory:');
}
/**
* @return PHPUnit_Extensions_Database_DataSet_IDataSet
*/
public function getDataSet()
{
return $this->createFlatXMLDataSet(dirname(__FILE__).'/_files/guestbook-seed.xml');
}
}
?>
~~~
### 实现 getConnection()
为了让清理与载入基境的功能正常运作,PHPUnit 数据库扩展模块需要用 PDO 库来实现跨供应商抽象访问数据库连接。重要的是要注意到,使用 PHPUnit 的数据库扩展模块并不要求应用程序本身基于PDO,PDO连接仅仅用于清理和建立基境。
在之前的例子里,我们在内存中创建 Sqlite 数据库并建立了连接,将此连接传递给 `createDefaultDBConnection` 方法,这个方法将 PDO 实例和第二参数(数据库名)包装在一个非常简单的数据库连接抽象层中,这个抽象层的类型是 `PHPUnit_Extensions_Database_DB_IDatabaseConnection`。
“使用数据库连接”一节解说了这个接口的API以及如何充分利用它们。
### 实现 getDataSet()
`getDataSet()` 方法定义了在每个测试执行之前的数据库初始状态应该是什么样。数据库的状态通过由 `PHPUnit_Extensions_Database_DataSet_IDataSet` 所代表的 DataSet(数据集)和由 `PHPUnit_Extensions_Database_DataSet_IDataTable`所代表的 DataTable(数据表)这两个概念进行抽象。下一节将详细讲述这些概念是如何运作的以及在数据库测试中使用它们有什么好处。
对于具体实现,只需要知道 `setUp()` 中会调用一次 `getDataSet()` 方法来接收基境数据集并将其插入数据库。在范例中使用了工厂方法 `createFlatXMLDataSet($filename)`,它代表一个用 XML 表示的数据集。
### 数据库构架(DDL)怎么办?
PHPUnit 假设在测试运行之前数据库以及其中的所有表(table)、触发器(trigger)、序列(Sequence)和视图(view)都已经创建好。这意味着开发者必须在运行测试套件之前确保数据库已经正确建立。
有几种方法来达成这个数据库测试的先决条件。
1.
如果使用的是持久化数据库(不是 Sqlite Memory),可以很轻松地用 phpMyAdmin(针对MySQL)之类的工具来一次性建立数据库,并在每个测试中复用这个数据库。
1.
如果使用的是诸如 [Doctrine 2](http://www.doctrine-project.org) 或 [Propel](http://www.propelorm.org/) 这样的库,可以用它们的API来在测试运行前一次性建立所需的数据库。可以利用 [PHPUnit 的引导和配置](#) 功能来在每次测试运行时执行这些代码。
### 小建议:使用你自己的抽象数据库 TestCase 类
从前面的实现范例中容易发现 `getConnection()` 方法是相当稳定的,可以在不同的数据库测试用例中重用。另外,为了保持测试的性能良好和数据库的开销较低,可以对代码进行一点重构,来为应用程序形成一个通用的抽象测试用例,同时依然可以为每个具体测试用例指定不同的数据基境:
~~~
<?php
abstract class MyApp_Tests_DatabaseTestCase extends PHPUnit_Extensions_Database_TestCase
{
// 只实例化 pdo 一次,供测试的清理和装载基境使用
static private $pdo = null;
// 对于每个测试,只实例化 PHPUnit_Extensions_Database_DB_IDatabaseConnection 一次
private $conn = null;
final public function getConnection()
{
if ($this->conn === null) {
if (self::$pdo == null) {
self::$pdo = new PDO('sqlite::memory:');
}
$this->conn = $this->createDefaultDBConnection(self::$pdo, ':memory:');
}
return $this->conn;
}
}
?>
~~~
这个例子里,数据库连接信息硬编码在 PDO 连接里了。PHPUnit 有另外一个绝妙的特性,可以让这个 TestCase 类更加通用。通过 [XML 配置](#) 可以为每个测试单独配置数据库连接信息。首先,在应用程序的 tests/ 目录下创建 “phpunit.xml” 文件,内容大体是这样:
~~~
<?xml version="1.0" encoding="UTF-8" ?>
<phpunit>
<php>
<var name="DB_DSN" value="mysql:dbname=myguestbook;host=localhost" />
<var name="DB_USER" value="user" />
<var name="DB_PASSWD" value="passwd" />
<var name="DB_DBNAME" value="myguestbook" />
</php>
</phpunit>
~~~
现在可以修改 TestCase 类了,像这样:
~~~
<?php
abstract class Generic_Tests_DatabaseTestCase extends PHPUnit_Extensions_Database_TestCase
{
// 只实例化 pdo 一次,供测试的清理和装载基境使用
static private $pdo = null;
// 对于每个测试,只实例化 PHPUnit_Extensions_Database_DB_IDatabaseConnection 一次
private $conn = null;
final public function getConnection()
{
if ($this->conn === null) {
if (self::$pdo == null) {
self::$pdo = new PDO( $GLOBALS['DB_DSN'], $GLOBALS['DB_USER'], $GLOBALS['DB_PASSWD'] );
}
$this->conn = $this->createDefaultDBConnection(self::$pdo, $GLOBALS['DB_DBNAME']);
}
return $this->conn;
}
}
?>
~~~
现在可以从命令行界面以不同的配置来运行数据库测试套件了:
~~~
user@desktop> phpunit --configuration developer-a.xml MyTests/
~~~
~~~
user@desktop> phpunit --configuration developer-b.xml MyTests/
~~~
在开发机上进行开发时能够轻松的针对不同的目标数据库来运行数据库测试显得非常重要。如果多个开发人员在同一个数据库连接上运行数据库测试,很容易因为竞态而导致测试失败。
- PHPUnit 手册
- 1. 安装 PHPUnit
- 需求
- PHP 档案包 (PHAR)
- Composer
- 可选的组件包
- 2. 编写 PHPUnit 测试
- 测试的依赖关系
- 数据供给器
- 对异常进行测试
- 对 PHP 错误进行测试
- 对输出进行测试
- 错误相关信息的输出
- 3. 命令行测试执行器
- 命令行选项
- 4. 基境(fixture)
- setUp() 多 tearDown() 少
- 变体
- 基境共享
- 全局状态
- 5. 组织测试
- 用文件系统来编排测试套件
- 用 XML 配置来编排测试套件
- 6. 有风险的测试
- 无用测试
- 意外的代码覆盖
- 测试执行期间产生的输出
- 测试执行时长的超时限制
- 全局状态篡改
- 7. 未完成的测试与跳过的测试
- 未完成的测试
- 跳过测试
- 用 @requires 来跳过测试
- 8. 数据库测试
- 数据库测试所支持的供应商
- 数据库测试的难点
- 数据库测试的四个阶段
- PHPUnit 数据库测试用例的配置
- 理解 DataSet(数据集)和 DataTable(数据表)
- 数据库连接 API
- 数据库断言 API
- 常见问题(FAQ)
- 9. 测试替身
- Stubs (桩件)
- 仿件对象(Mock Object)
- Prophecy
- 对特质(Trait)与抽象类进行模仿
- 对 Web 服务(Web Services)进行上桩或模仿
- 对文件系统进行模仿
- 10. 测试实践
- 在开发过程中
- 在调试过程中
- 11. 代码覆盖率分析
- 用于代码覆盖率的软件衡量标准
- 包含与排除文件
- 略过代码块
- 指明要覆盖的方法
- 边缘情况
- 12. 测试的其他用途
- 敏捷文档
- 跨团队测试
- 13. Logging (日志记录)
- 测试结果 (XML)
- 测试结果 (TAP)
- 测试结果 (JSON)
- 代码覆盖率 (XML)
- 代码覆盖率 (TEXT)
- 14. 扩展 PHPUnit
- 从 PHPUnit_Framework_TestCase 派生子类
- 编写自定义断言
- 实现 PHPUnit_Framework_TestListener
- 从 PHPUnit_Extensions_TestDecorator 派生子类
- 实现 PHPUnit_Framework_Test
- A. 断言
- assertArrayHasKey()
- assertClassHasAttribute()
- assertArraySubset()
- assertClassHasStaticAttribute()
- assertContains()
- assertContainsOnly()
- assertContainsOnlyInstancesOf()
- assertCount()
- assertEmpty()
- assertEqualXMLStructure()
- assertEquals()
- assertFalse()
- assertFileEquals()
- assertFileExists()
- assertGreaterThan()
- assertGreaterThanOrEqual()
- assertInfinite()
- assertInstanceOf()
- assertInternalType()
- assertJsonFileEqualsJsonFile()
- assertJsonStringEqualsJsonFile()
- assertJsonStringEqualsJsonString()
- assertLessThan()
- assertLessThanOrEqual()
- assertNan()
- assertNull()
- assertObjectHasAttribute()
- assertRegExp()
- assertStringMatchesFormat()
- assertStringMatchesFormatFile()
- assertSame()
- assertStringEndsWith()
- assertStringEqualsFile()
- assertStringStartsWith()
- assertThat()
- assertTrue()
- assertXmlFileEqualsXmlFile()
- assertXmlStringEqualsXmlFile()
- assertXmlStringEqualsXmlString()
- B. 标注
- @author
- @after
- @afterClass
- @backupGlobals
- @backupStaticAttributes
- @before
- @beforeClass
- @codeCoverageIgnore*
- @covers
- @coversDefaultClass
- @coversNothing
- @dataProvider
- @depends
- @expectedException
- @expectedExceptionCode
- @expectedExceptionMessage
- @expectedExceptionMessageRegExp
- @group
- @large
- @medium
- @preserveGlobalState
- @requires
- @runTestsInSeparateProcesses
- @runInSeparateProcess
- @small
- @test
- @testdox
- @ticket
- @uses
- C. XML 配置文件
- PHPUnit
- 测试套件
- 分组
- 为代码覆盖率包含或排除文件
- Logging (日志记录)
- 测试监听器
- 设定 PHP INI 设置、常量、全局变量
- 为 Selenium RC 配置浏览器
- D. 升级
- E. 索引
- F. 参考书目
- G. 版权