我如何对具有副作用的方法进行单元测试?

时间:2012-11-06 15:39:37

标签: php unit-testing phpunit

鉴于以下实施:

class Foo {
    public function helper() {
        // Does something with external side effects, like updating
        // a database or altering the file system.
    }

    public function bar() {
        if ($this->helper() === FALSE) {
            throw new Exception(/* ... */);
        }
    }
}

我如何在测试过程中对Foo::bar()的副作用进行单元测试Foo::helper()

我知道我可以模仿Foo和存根Foo::helper()

public function testGoodBar() {
    $mock = $this->getMock('Foo', array('helper'));
    $this->expects($this->once())
        ->method('helper')
        ->will($this->returnValue(TRUE));

    $this->assertTrue($mock->bar());
}

...但是这使得测试对于引入可能具有副作用的其他方法的代码更改大开。然后,如果再次运行测试而不进行更新,则测试本身将产生永久的副作用。

我也可以模仿Foo,这样它的所有方法都会被嘲笑而且不会产生副作用:

public function testGoodBar() {
    $mock = $this->getMock('Foo');
    $this->expects($this->once())
        ->method('helper')
        ->will($this->returnValue(TRUE));

    $this->assertTrue($mock->bar());
}

...但是偶然Foo::bar()被嘲笑,这很糟糕,因为这是我们想要测试的方法。

我能想出的唯一解决方案是明确地模拟所有方法,除了正在测试的方法:

public function testGoodBar() {
    $mock = $this->getMock('Foo', array_diff(
        get_class_methods('Foo'),
        'bar'
    ));

    $this->expects($this->once())
        ->method('helper')
        ->will($this->returnValue(TRUE));

    $this->assertTrue($mock->bar());
}

......但这似乎很糟糕,我觉得我错过了一些明显的东西。

2 个答案:

答案 0 :(得分:4)

(在这个问题的答案中考虑问题下的评论。)

如果您正在扩展一个唯一目的是产生副作用的类,我希望所有扩展代码也会产生副作用。因此,您必须在测试中考虑到这一点,并设置一个环境,您可以在其中测试具有副作用的代码(即:为此测试启动并运行memcached实例)。

如果你不想要这个(可以理解),那么最好将你的代码编写为副作用类的包装器,使其成为可模拟的。因此,您的Foo::__construct接受产生副作用的类的实例或工厂,因此您可以在测试中模拟它以仅测试无副作用的代码。

答案 1 :(得分:-1)

看起来你应该更关心测试helper()调用的方法,在这种情况下更少关注bar()。

有时当一个方法执行很多操作时,最好将其分解并让其他方法执行这些操作。执行此操作时,您希望输出与以前相同,但代码会分解为更易于管理的部分。首先测试最小的部分是很重要的。当你进入测试bar()时,你不需要测试它,所以你不必一遍又一遍地模拟它。因为很多案例都会在helper()的支持方法中介绍。

相关问题