PHPUnit - 循环依赖

时间:2012-02-15 17:30:35

标签: php phpunit circular-dependency

我有一个看起来像这样的课程:

class Foo {
    protected $_arr = array();

    public function has($key) {
        return array_key_exists($key, $this->_arr);
    }

    public function add($key, $val) {
        $this->_arr[$key] = $val;
    }
}

对于我对这些方法的PHPUnit测试,我认为测试add()的唯一方法是断言has()在添加后为同一个键返回TRUE。这使我的testAdd()测试依赖于我的testHas()测试。相反,我能想到测试has()的唯一方法基本上是完成相同的步骤,但这会使这个测试依赖于已经依赖的测试,产生鸡和蛋类型的问题。

我是以错误的方式来做这件事的吗?测试这样的东西有什么更好的方法?

4 个答案:

答案 0 :(得分:2)

不是编写每个方法一个测试,而是围绕类必须提供的功能设计测试。您将拥有使用多种方法的测试,但这很好,因为它向开发人员表明这些方法是相关的。这就是测试驱动开发 - 在为类编写代码之前编写测试时编写测试的地方 - 真的很棒。

class FooTest extends PHPUnit_Framework_TestCase
{
    function testStartsEmpty() {
        $foo = new Foo;
        self::assertFalse($foo->has('bar'));
    }

    function testAddStoresKeys() {
        $foo = new Foo;
        $foo->add('bar', 'baz');
        self::assertTrue($foo->has('bar'));
    }

    function testAddStoresKeysWithNullValues() {
        $foo = new Foo;
        $foo->add('bar', null);
        self::assertTrue($foo->has('bar'));
    }

    function testAddStoresValues() {
        $foo = new Foo;
        $foo->add('bar', 'baz');
        self::assertEquals('baz', $foo->get('bar'));  // fatal error
    }
}

现在testAddStoresValues()失败了,是时候实现一个方法来获取给定键的值:Foo::get($key)

答案 1 :(得分:1)

PHPUnit allows testing of non-public members

然而,使用$sut->has()查明$sut->add()是否有效是完全没问题的。此外,当您测试$sut->add()时,您也不需要为$sut->has()编写单独的测试,因为它已经涵盖在$sut->add()测试中。只需添加@covers注释。

答案 2 :(得分:1)

参考@Gordon回答的评论中的长篇讨论:

  

单元测试中的单位是对象而不是方法!您不希望独立于对象的其他方法测试方法。 (如果你想这样做只是使用函数;)) - 测试对象的行为以及它是否在调用时做预期的思考而不是方法如何在内部进行交互是非常重要的。

我最近写了一篇博文,解释了为什么不单独测试单个方法很重要:

http://edorian.posterous.com/the-unit-in-unit-testing

从我的帖子中引用:

  

PHP中的单元测试是关于测试类的可观察行为

最重要的是:

  

从外面观察!如果类永远不会改变方法调用的结果,那么没有人关心类的内部状态。


拿这个样本:

public function setValue($value) {
    $this->value = $value;
}

public function execute() {
    if (!$this->value) {
        throw new Exception("No Value, no good");
    }
    return $value * 10; // business logic
}

听起来微不足道但是这种区别很重要,而且在查看更大的课程时更容易忽视。

我们在那里测试什么?

  • 如果我们没有setValue AND然后调用execute会抛出异常!
  • 如果我们设置setValue然后调用execute我们得到一个计算结果

因此,我们正在测试您班级的两种行为,而不是孤立的方法!

答案 3 :(得分:0)

IMO可以测试对象的行为。因此,您可以测试之前是否返回false,并在将一些内容添加到集合中后为true。