Chinaunix首页 | 论坛 | 博客
  • 博客访问: 510724
  • 博文数量: 130
  • 博客积分: 10060
  • 博客等级: 上将
  • 技术积分: 1720
  • 用 户 组: 普通用户
  • 注册时间: 2007-12-21 12:35
文章分类

全部博文(130)

文章存档

2011年(2)

2010年(9)

2009年(41)

2008年(78)

我的朋友

分类:

2008-04-01 18:12:16

第 11 章 模拟对象

有时你需要检查某对象是否被正确调用了。看个例子:假如我们要测试是否正确调用了某观测另一对象的对象的方法,本例中是update()

在中,我们先用类PHPUnit_Framework_TestCase(见)提供的getMock()方法为Observer装配一个模拟对象。我们给出一个数组作为getMock()方法的第二个参数(可选的),因此只有类Observerupdate()方法被替换为模拟实现。

然后我们使用PHPUnit提供的指定仿制品的行为和期望值。大体上,这意味着你不需要创建若干临时对象——例如,一个用于指定你期望update()方法被调用,另一个用于期望的参数——然后把它们绑在一起配置期望值。 如例中所示,改为链接方法调用。这使得代码更易读和“流畅”。

范例 11.1: 测试某方法被附以指定的参数调用一次


require_once 'PHPUnit/Framework.php';
 
class ObserverTest extends PHPUnit_Framework_TestCase
{
    public function testUpdateIsCalledOnce()
    {
        // 为Observer类创建一个只模拟update()方法的模拟对象。
        $observer = $this->getMock('Observer', array('update'));
 
        // 设定update()方法的期望值为只被以字符串“something”为参数调用一次。
        $observer->expects($this->once())
                 ->method('update')
                 ->with($this->equalTo('something'));
 
        // 创建一个Subject对象并附上模拟的Observer对象。
        $subject = new Subject;
        $subject->attach($observer);
 
        // 调用$subject对象的doSomething()方法,我们期望该方法
        // 以字符串“something”为参数调用模拟的Observer对象的update()方法。
        $subject->doSomething();
    }
}
?>

列出可用的匹配器,它们可用于表示期望模拟的方法要被运行多少次。

表 11.1. 匹配器

匹配器含义
PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount any()返回一个匹配器,当它评估的方法被执行0或多次时匹配。
PHPUnit_Framework_MockObject_Matcher_InvokedCount never()返回一个匹配器,当它评估的方法被从未被执行时匹配。
PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastOnce atLeastOnce()返回一个匹配器,当它评估的方法被至少执行一次时匹配。
PHPUnit_Framework_MockObject_Matcher_InvokedCount once()返回一个匹配器,当它评估的方法恰好被执行一次时匹配。
PHPUnit_Framework_MockObject_Matcher_InvokedCount exactly(int $count)返回一个匹配器,当它评估的方法正好被执行$count次时匹配。
PHPUnit_Framework_MockObject_Matcher_InvokedAtIndex at(int $index)返回一个匹配器,当它评估的方法在特定的$index上被调用时匹配。

在中可以找到一些约束条件,它们可被一起用于这些匹配器。

自分流

另外,你能应用自分流模式代替模拟对象来测试Subject实现。该方法将测试用例自身用作存根。术语自分流来自医学行业的实践,安装管子从动脉取血并放回静脉来为注射麻药提供合适的位置。

首先,我们让测试用例类实现Observer,它是要观测Subject的对象要实现的接口:

class ObserverTest extends PHPUnit_Framework_TestCase implements Observer
{
}

下一步,我们实现一个Observer方法,update(),来检查当被观测的Subject对象的状态改变时该方法被调用:

public $wasCalled = FALSE;

public function update(Subject $subject)
{
$this->wasCalled = TRUE;
}

现在我们可以写测试了。我们创建一个新的Subject对象并附上测试对象作为观测者。当Subject的状态改变——例如,通过调用它的doSomething()方法——Subject对象必须调用注册为观测者的所有对象的update()方法。我们用我们的update()实现中设置的$wasCalled实例变量检查Subject对象是否做期望的事情:

public function testUpdate()
{
$subject = new Subject;
$subject->attach($this);
$subject->doSomething();

$this->assertTrue($this->wasCalled);
}

注意我们创建一个新的Subject对象而不是依赖一个全局实例。存根鼓励这种形式的设计。它降低对象间的耦合并改善重用性。

如果你不熟悉自分流模式,(会感觉)测试很难阅读。这是什么?为什么一个测试用例同时也是个观测者?一旦你习惯这种用语,测试就易于阅读了。要了解的是测试的所有东西在同一个类中。

存根

只测试一件事情的测试比可能在很多地方产生失败的测试更有益。如何排除外部因素对测试的影响?简单地说,就是把花费大、杂乱、不可靠、速度慢或复杂的资源 换成为你的测试起见自动产生的存根。例如,你可以用常量替换实际上非常复杂的计算,至少可以针对单个测试(这样做)。

存根解决了分配代价高昂的外部资源的问题。例如,在测试间共享类似数据库连接有用,但为测试起见从根本上避免使用数据库会更好。

显示如何存根化方法调用以及装配返回值。

范例 11.2: 存根化一个方法调用


require_once 'PHPUnit/Framework.php';
 
class StubTest extends PHPUnit_Framework_TestCase
{
    public function testStub()
    {
        $stub = $this->getMock('SomeClass', array('doSomething'));
        $stub->expects($this->any())
             ->method('doSomething')
             ->will($this->returnValue('foo'));
 
        // 调用$stub->doSomething()会立刻返回“foo”。
    }
}
?>

列出可用来为存根方法调用配置返回值的方法。

表11.2. 存根API

方法含义
PHPUnit_Framework_MockObject_Stub_Exception throwException(Exception $exception)设定对方法的所有调用都会跑出的异常。
PHPUnit_Framework_MockObject_Stub_Return returnValue(mixed $value)设定对方法的所有调用都返回$value
PHPUnit_Framework_MockObject_Stub_ConsecutiveCalls onConsecutiveCalls(mixed $value, ...)为方法的连续调用装配不同的返回值。

此外,你能自己编写存根并据此改善你的设计。通过单个façade(façade模式?)访问广泛使用的资源,这样就能方便地用存根替换资源。例如,你有个Database对象,接口IDatabase的实现,而不是让直接数据库访问散落在代码各处。然后就能创建一个实现了IDatabase的存根并用于你的测试。你甚至可以创建一个选项,用于(选择)带存根数据库或真实的数据库运行测试,这样你的测试就既能在开发期间用于本地测试,又能用于真实数据库的综合测试。

需要存根化的功能趋向于集成在同一个对象中,提高了内聚性。通过使用单个内聚的接口引入各种功能,你就降低了同系统其他部分的耦合。

阅读(1634) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~