Stubs (樁件)

2018-02-24 15:41 更新

Stubs (樁件)

將對象替換為(可選地)返回配置好的返回值的測試替身的實踐方法稱為上樁(stubbing)??梢杂?em>樁件(stub)來“替換掉被測系統(tǒng)所依賴的實際組件,這樣測試就有了對被測系統(tǒng)的間接輸入的控制點(diǎn)。這使得測試能強(qiáng)制安排被測系統(tǒng)的執(zhí)行路徑,否則被測系統(tǒng)可能無法執(zhí)行”。

Example?9.2, “對某個方法的調(diào)用上樁,返回固定值”展示了如何對方法的調(diào)用上樁以及如何設(shè)定返回值。首先用 PHPUnit_Framework_TestCase 類提供的 getMockBuilder() 方法來建立一個樁件對象,它表面看起來像是 SomeClass類(Example?9.1, “需要對其上樁的類”)的實例。隨后用 PHPUnit 提供的 流暢式接口來指定樁件的行為。本質(zhì)上,這意味著不需要建立多個臨時對象然后再把它們捆到一起。取而代之的是范例中所示的鏈?zhǔn)椒椒ㄕ{(diào)用。這使得代碼更加易讀并更加“流暢”。

Example?9.1.?需要對其上樁的類

<?php
class SomeClass
{
    public function doSomething()
    {
        // 隨便做點(diǎn)什么。
    }
}
?>

Example?9.2.?對某個方法的調(diào)用上樁,返回固定值

<?php
require_once 'SomeClass.php';

class StubTest extends PHPUnit_Framework_TestCase
{
    public function testStub()
    {
        // 為 SomeClass 類創(chuàng)建樁件。
        $stub = $this->getMockBuilder('SomeClass')
                     ->getMock();

        // 配置樁件。
        $stub->method('doSomething')
             ->willReturn('foo');

        // 現(xiàn)在調(diào)用 $stub->doSomething() 將返回 'foo'。
        $this->assertEquals('foo', $stub->doSomething());
    }
}
?>

局限性:名字為“method”的方法

僅當(dāng)原始類中不包含名字為“method”的方法時,以上范例才能正常運(yùn)行。

如果原始類包含名為“method”的方法,就必須用

$stub->expects($this->any())->method('doSomething')->willReturn('foo');

“在幕后”,當(dāng)使用了 getMock() 方法時, PHPUnit 自動生成了一個新的 PHP 類來實現(xiàn)想要的行為。

Example?9.3, “使用可用于配置生成的測試替身類的仿件生成器 API”這個例子展示了如何用仿件生成器的流暢式接口來配置測試替身的生成。

Example?9.3.?使用可用于配置生成的測試替身類的仿件生成器 API

<?php
require_once 'SomeClass.php';

class StubTest extends PHPUnit_Framework_TestCase
{
    public function testStub()
    {
        // 為 SomeClass 類創(chuàng)建樁件。
        $stub = $this->getMockBuilder('SomeClass')
                     ->disableOriginalConstructor()
                     ->getMock();

        // 配置樁件。
        $stub->method('doSomething')
             ->willReturn('foo');

        // 現(xiàn)在調(diào)用 $stub->doSomething() 將返回 'foo'。
        $this->assertEquals('foo', $stub->doSomething());
    }
}
?>

以下是仿件生成器提供的方法列表:

  • setMethods(array $methods) 可以在仿件生成器對象上調(diào)用,來指定哪些方法將被替換為可配置的測試替身。其他方法的行為不會有所改變。如果調(diào)用 setMethods(null),那么沒有方法會被替換。

  • setConstructorArgs(array $args) 可用于向原版類的構(gòu)造函數(shù)(默認(rèn)情況下不會被替換為偽實現(xiàn))提供參數(shù)數(shù)組。

  • setMockClassName($name) 可用于指定生成的測試替身類的類名。

  • disableOriginalConstructor() 參數(shù)可用于禁用對原版類的構(gòu)造方法的調(diào)用。

  • disableOriginalClone() 可用于禁用對原版類的克隆方法的調(diào)用。

  • disableAutoload()可用于在測試替身類的生成期間禁用 __autoload()

在之前的例子中,用 willReturn($value) 返回簡單值。這個簡短的語法相當(dāng)于 will($this->returnValue($value))。而在這個長點(diǎn)的語法中,可以使用變量,從而實現(xiàn)更復(fù)雜的上樁行為。

有時想要將(未改變的)方法調(diào)用時所使用的參數(shù)之一作為樁件的方法的調(diào)用結(jié)果來返回。 Example?9.4, “對某個方法的調(diào)用上樁,返回參數(shù)之一”展示了如何用 returnArgument() 代替 returnValue() 來做到這點(diǎn)。

Example?9.4.?對某個方法的調(diào)用上樁,返回參數(shù)之一

<?php
require_once 'SomeClass.php';

class StubTest extends PHPUnit_Framework_TestCase
{
    public function testReturnArgumentStub()
    {
        // 為 SomeClass 類創(chuàng)建樁件。
        $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'));
    }
}
?>

在用流暢式接口進(jìn)行測試時,讓某個已上樁的方法返回對樁件對象的引用有時會很有用。Example?9.5, “對方法的調(diào)用上樁,返回對樁件對象的引用”展示了如何用 returnSelf() 來做到這點(diǎn)。

Example?9.5.?對方法的調(diào)用上樁,返回對樁件對象的引用

<?php
require_once 'SomeClass.php';

class StubTest extends PHPUnit_Framework_TestCase
{
    public function testReturnSelf()
    {
        // 為 SomeClass 類創(chuàng)建樁件。
        $stub = $this->getMockBuilder('SomeClass')
                     ->getMock();

        // 配置樁件。
        $stub->method('doSomething')
             ->will($this->returnSelf());

        // $stub->doSomething() 返回 $stub
        $this->assertSame($stub, $stub->doSomething());
    }
}
?>

有時候,上樁的方法需要根據(jù)預(yù)定義的參數(shù)清單來返回不同的值。可以用 returnValueMap() 方法將參數(shù)和相應(yīng)的返回值關(guān)聯(lián)起來建立映射。范例參見Example?9.6, “對方法的調(diào)用上樁,按照映射確定返回值”。

Example?9.6.?對方法的調(diào)用上樁,按照映射確定返回值

<?php
require_once 'SomeClass.php';

class StubTest extends PHPUnit_Framework_TestCase
{
    public function testReturnValueMapStub()
    {
        // 為 SomeClass 類創(chuàng)建樁件。
        $stub = $this->getMockBuilder('SomeClass')
                     ->getMock();

        // 創(chuàng)建從參數(shù)到返回值的映射。
        $map = array(
          array('a', 'b', 'c', 'd'),
          array('e', 'f', 'g', 'h')
        );

        // 配置樁件。
        $stub->method('doSomething')
             ->will($this->returnValueMap($map));

        // $stub->doSomething() 根據(jù)提供的參數(shù)返回不同的值。
        $this->assertEquals('d', $stub->doSomething('a', 'b', 'c'));
        $this->assertEquals('h', $stub->doSomething('e', 'f', 'g'));
    }
}
?>

如果上樁的方法需要返回計算得到的值而不是固定值(參見 returnValue())或某個(未改變的)參數(shù)(參見 returnArgument()),可以用 returnCallback() 來讓上樁的方法返回回調(diào)函數(shù)或方法的結(jié)果。范例參見Example?9.7, “對方法的調(diào)用上樁,由回調(diào)生成返回值”

Example?9.7.?對方法的調(diào)用上樁,由回調(diào)生成返回值

<?php
require_once 'SomeClass.php';

class StubTest extends PHPUnit_Framework_TestCase
{
    public function testReturnCallbackStub()
    {
        // 為 SomeClass 類創(chuàng)建樁件。
        $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'));
    }
}
?>

相比于建立回調(diào)方法,有一個更簡單的選擇是直接給出期望返回值的列表??梢杂?onConsecutiveCalls() 方法來做到這個。范例參見 Example?9.8, “對方法的調(diào)用上樁,按照指定順序返回列表中的值”。

Example?9.8.?對方法的調(diào)用上樁,按照指定順序返回列表中的值

<?php
require_once 'SomeClass.php';

class StubTest extends PHPUnit_Framework_TestCase
{
    public function testOnConsecutiveCallsStub()
    {
        // 為 SomeClass 類創(chuàng)建樁件。
        $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, “對方法的調(diào)用上樁,拋出異?!?/a>展示了如何用 throwException() 做到這點(diǎn)。

Example?9.9.?對方法的調(diào)用上樁,拋出異常

<?php
require_once 'SomeClass.php';

class StubTest extends PHPUnit_Framework_TestCase
{
    public function testThrowExceptionStub()
    {
        // 為 SomeClass 類創(chuàng)建樁件。
        $stub = $this->getMockBuilder('SomeClass')
                     ->getMock();

        // 配置樁件。
        $stub->method('doSomething')
             ->will($this->throwException(new Exception));

        // $stub->doSomething() 拋出異常
        $stub->doSomething();
    }
}
?>

另外,也可以自行編寫樁件,并在此過程中改善設(shè)計。在系統(tǒng)中被廣泛使用的資源是通過單個外觀(facade)來訪問的,因此很容易就能用樁件替換掉資源。例如,將散落在代碼各處的對數(shù)據(jù)庫的直接調(diào)用替換為單個 Database 對象,這個對象實現(xiàn)了 IDatabase 接口。接下來,就可以創(chuàng)建實現(xiàn)了 IDatabase 的樁件并在測試中使用之。甚至可以創(chuàng)建一個選項來控制是用樁件還是用真實數(shù)據(jù)庫來運(yùn)行測試,這樣測試就既能在開發(fā)過程中用作本地測試,又能在實際數(shù)據(jù)庫環(huán)境中進(jìn)行集成測試。

需要上樁的功能往往集中在同一個對象中,這就改善了內(nèi)聚度。將功能通過單一且一致的界面呈現(xiàn)出來,就降低了這部分與系統(tǒng)其他部分之間的耦合度。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號