# Stubs (樁件)
將對象替換為(可選地)返回配置好的返回值的測試替身的實踐方法稱為*上樁(stubbing)*。可以用*樁件(stub)*來“替換掉被測系統所依賴的實際組件,這樣測試就有了對被測系統的間接輸入的控制點。這使得測試能強制安排被測系統的執行路徑,否則被測系統可能無法執行”。
[Example?9.2, “對某個方法的調用上樁,返回固定值”](# "Example?9.2.?對某個方法的調用上樁,返回固定值")展示了如何對方法的調用上樁以及如何設定返回值。首先用 `PHPUnit_Framework_TestCase` 類提供的 `getMockBuilder()` 方法來建立一個樁件對象,它表面看起來像是 `SomeClass`類([Example?9.1, “需要對其上樁的類”](# "Example?9.1.?需要對其上樁的類"))的實例。隨后用 PHPUnit 提供的 [流暢式接口](http://martinfowler.com/bliki/FluentInterface.html)來指定樁件的行為。本質上,這意味著不需要建立多個臨時對象然后再把它們捆到一起。取而代之的是范例中所示的鏈式方法調用。這使得代碼更加易讀并更加“流暢”。
**Example?9.1.?需要對其上樁的類**
~~~
<?php
class SomeClass
{
public function doSomething()
{
// 隨便做點什么。
}
}
?>
~~~
**Example?9.2.?對某個方法的調用上樁,返回固定值**
~~~
<?php
require_once 'SomeClass.php';
class StubTest extends PHPUnit_Framework_TestCase
{
public function testStub()
{
// 為 SomeClass 類創建樁件。
$stub = $this->getMockBuilder('SomeClass')
->getMock();
// 配置樁件。
$stub->method('doSomething')
->willReturn('foo');
// 現在調用 $stub->doSomething() 將返回 'foo'。
$this->assertEquals('foo', $stub->doSomething());
}
}
?>
~~~
### 局限性:名字為“method”的方法
僅當原始類中不包含名字為“method”的方法時,以上范例才能正常運行。
如果原始類包含名為“method”的方法,就必須用
~~~
$stub->expects($this->any())->method('doSomething')->willReturn('foo');
~~~
“在幕后”,當使用了 `getMock()` 方法時, PHPUnit 自動生成了一個新的 PHP 類來實現想要的行為。
[Example?9.3, “使用可用于配置生成的測試替身類的仿件生成器 API”](# "Example?9.3.?使用可用于配置生成的測試替身類的仿件生成器 API")這個例子展示了如何用仿件生成器的流暢式接口來配置測試替身的生成。
**Example?9.3.?使用可用于配置生成的測試替身類的仿件生成器 API**
~~~
<?php
require_once 'SomeClass.php';
class StubTest extends PHPUnit_Framework_TestCase
{
public function testStub()
{
// 為 SomeClass 類創建樁件。
$stub = $this->getMockBuilder('SomeClass')
->disableOriginalConstructor()
->getMock();
// 配置樁件。
$stub->method('doSomething')
->willReturn('foo');
// 現在調用 $stub->doSomething() 將返回 'foo'。
$this->assertEquals('foo', $stub->doSomething());
}
}
?>
~~~
以下是仿件生成器提供的方法列表:
-
`setMethods(array $methods)` 可以在仿件生成器對象上調用,來指定哪些方法將被替換為可配置的測試替身。其他方法的行為不會有所改變。如果調用 `setMethods(null)`,那么沒有方法會被替換。
-
`setConstructorArgs(array $args)` 可用于向原版類的構造函數(默認情況下不會被替換為偽實現)提供參數數組。
-
`setMockClassName($name)` 可用于指定生成的測試替身類的類名。
-
`disableOriginalConstructor()` 參數可用于禁用對原版類的構造方法的調用。
-
`disableOriginalClone()` 可用于禁用對原版類的克隆方法的調用。
-
`disableAutoload()`可用于在測試替身類的生成期間禁用 `__autoload()`。
在之前的例子中,用 `willReturn($value)` 返回簡單值。這個簡短的語法相當于 `will($this->returnValue($value))`。而在這個長點的語法中,可以使用變量,從而實現更復雜的上樁行為。
有時想要將(未改變的)方法調用時所使用的參數之一作為樁件的方法的調用結果來返回。 [Example?9.4, “對某個方法的調用上樁,返回參數之一”](# "Example?9.4.?對某個方法的調用上樁,返回參數之一")展示了如何用 `returnArgument()` 代替 `returnValue()` 來做到這點。
**Example?9.4.?對某個方法的調用上樁,返回參數之一**
~~~
<?php
require_once 'SomeClass.php';
class StubTest extends PHPUnit_Framework_TestCase
{
public function testReturnArgumentStub()
{
// 為 SomeClass 類創建樁件。
$stub = $this->getMockBuilder('SomeClass')
->getMock();
// 配置樁件。
$stub->method('doSomething')
->will($this->returnArgument(0));
// stub->doSomething('foo') 返回 'foo'
$this->assertEquals('foo', $stub->doSomething('foo'));
// $stub->doSomething('bar') 返回 'bar'
$this->assertEquals('bar', $stub->doSomething('bar'));
}
}
?>
~~~
在用流暢式接口進行測試時,讓某個已上樁的方法返回對樁件對象的引用有時會很有用。[Example?9.5, “對方法的調用上樁,返回對樁件對象的引用”](# "Example?9.5.?對方法的調用上樁,返回對樁件對象的引用")展示了如何用 `returnSelf()` 來做到這點。
**Example?9.5.?對方法的調用上樁,返回對樁件對象的引用**
~~~
<?php
require_once 'SomeClass.php';
class StubTest extends PHPUnit_Framework_TestCase
{
public function testReturnSelf()
{
// 為 SomeClass 類創建樁件。
$stub = $this->getMockBuilder('SomeClass')
->getMock();
// 配置樁件。
$stub->method('doSomething')
->will($this->returnSelf());
// $stub->doSomething() 返回 $stub
$this->assertSame($stub, $stub->doSomething());
}
}
?>
~~~
有時候,上樁的方法需要根據預定義的參數清單來返回不同的值。可以用 `returnValueMap()` 方法將參數和相應的返回值關聯起來建立映射。范例參見[Example?9.6, “對方法的調用上樁,按照映射確定返回值”](# "Example?9.6.?對方法的調用上樁,按照映射確定返回值")。
**Example?9.6.?對方法的調用上樁,按照映射確定返回值**
~~~
<?php
require_once 'SomeClass.php';
class StubTest extends PHPUnit_Framework_TestCase
{
public function testReturnValueMapStub()
{
// 為 SomeClass 類創建樁件。
$stub = $this->getMockBuilder('SomeClass')
->getMock();
// 創建從參數到返回值的映射。
$map = array(
array('a', 'b', 'c', 'd'),
array('e', 'f', 'g', 'h')
);
// 配置樁件。
$stub->method('doSomething')
->will($this->returnValueMap($map));
// $stub->doSomething() 根據提供的參數返回不同的值。
$this->assertEquals('d', $stub->doSomething('a', 'b', 'c'));
$this->assertEquals('h', $stub->doSomething('e', 'f', 'g'));
}
}
?>
~~~
如果上樁的方法需要返回計算得到的值而不是固定值(參見 `returnValue()`)或某個(未改變的)參數(參見 `returnArgument()`),可以用 `returnCallback()` 來讓上樁的方法返回回調函數或方法的結果。范例參見[Example?9.7, “對方法的調用上樁,由回調生成返回值”](# "Example?9.7.?對方法的調用上樁,由回調生成返回值")。
**Example?9.7.?對方法的調用上樁,由回調生成返回值**
~~~
<?php
require_once 'SomeClass.php';
class StubTest extends PHPUnit_Framework_TestCase
{
public function testReturnCallbackStub()
{
// 為 SomeClass 類創建樁件。
$stub = $this->getMockBuilder('SomeClass')
->getMock();
// 配置樁件。
$stub->method('doSomething')
->will($this->returnCallback('str_rot13'));
// $stub->doSomething($argument) 返回 str_rot13($argument)
$this->assertEquals('fbzrguvat', $stub->doSomething('something'));
}
}
?>
~~~
相比于建立回調方法,有一個更簡單的選擇是直接給出期望返回值的列表。可以用 `onConsecutiveCalls()` 方法來做到這個。范例參見 [Example?9.8, “對方法的調用上樁,按照指定順序返回列表中的值”](# "Example?9.8.?對方法的調用上樁,按照指定順序返回列表中的值")。
**Example?9.8.?對方法的調用上樁,按照指定順序返回列表中的值**
~~~
<?php
require_once 'SomeClass.php';
class StubTest extends PHPUnit_Framework_TestCase
{
public function testOnConsecutiveCallsStub()
{
// 為 SomeClass 類創建樁件。
$stub = $this->getMockBuilder('SomeClass')
->getMock();
// 配置樁件。
$stub->method('doSomething')
->will($this->onConsecutiveCalls(2, 3, 5, 7));
// $stub->doSomething() 每次返回值都不同
$this->assertEquals(2, $stub->doSomething());
$this->assertEquals(3, $stub->doSomething());
$this->assertEquals(5, $stub->doSomething());
}
}
?>
~~~
除了返回一個值之外,上樁的方法還能拋出一個異常。[Example?9.9, “對方法的調用上樁,拋出異常”](# "Example?9.9.?對方法的調用上樁,拋出異常")展示了如何用 `throwException()` 做到這點。
**Example?9.9.?對方法的調用上樁,拋出異常**
~~~
<?php
require_once 'SomeClass.php';
class StubTest extends PHPUnit_Framework_TestCase
{
public function testThrowExceptionStub()
{
// 為 SomeClass 類創建樁件。
$stub = $this->getMockBuilder('SomeClass')
->getMock();
// 配置樁件。
$stub->method('doSomething')
->will($this->throwException(new Exception));
// $stub->doSomething() 拋出異常
$stub->doSomething();
}
}
?>
~~~
另外,也可以自行編寫樁件,并在此過程中改善設計。在系統中被廣泛使用的資源是通過單個外觀(facade)來訪問的,因此很容易就能用樁件替換掉資源。例如,將散落在代碼各處的對數據庫的直接調用替換為單個 `Database` 對象,這個對象實現了 `IDatabase` 接口。接下來,就可以創建實現了 `IDatabase` 的樁件并在測試中使用之。甚至可以創建一個選項來控制是用樁件還是用真實數據庫來運行測試,這樣測試就既能在開發過程中用作本地測試,又能在實際數據庫環境中進行集成測試。
需要上樁的功能往往集中在同一個對象中,這就改善了內聚度。將功能通過單一且一致的界面呈現出來,就降低了這部分與系統其他部分之間的耦合度。
- 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. 版權