# 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. 版權