TDD - 无法模拟的依赖关系

Tom*_*m B 13 php testing tdd phpunit unit-testing

假设我有一节课:

class XMLSerializer {
    public function serialize($object) {
        $document = new DomDocument();
        $root = $document->createElement('object');
        $document->appendChild($root);

        foreach ($object as $key => $value) {
            $root->appendChild($document->createElement($key, $value);
        }

        return $document->saveXML();
    }

    public function unserialze($xml) {
        $document = new DomDocument();
        $document->loadXML($xml);

        $root = $document->getElementsByTagName('root')->item(0);

        $object = new stdclass;
        for ($i = 0; $i < $root->childNodes->length; $i++) {
            $element = $root->childNodes->item($i);
            $tagName = $element->tagName;
            $object->$tagName = $element->nodeValue();
        }

        return $object;
    }

}
Run Code Online (Sandbox Code Playgroud)

我该如何单独测试?在测试这个类时,我也在测试DomDocument类

我可以传入文档对象:

class XMLSerializer {
    private $document;

    public function __construct(\DomDocument $document) {
        $this->document = $document;
    }

    public function serialize($object) {
        $root = $this->document->createElement('object');
        $this->document->appendChild($root);

        foreach ($object as $key => $value) {
            $root->appendChild($this->document->createElement($key, $value);
        }

        return $this->document->saveXML();
    }

    public function unserialze($xml) {
        $this->document->loadXML($xml);

        $root = $this->document->getElementsByTagName('root')->item(0);

        $object = new stdclass;
        for ($i = 0; $i < $root->childNodes->length; $i++) {
            $element = $root->childNodes->item($i);
            $tagName = $element->tagName;
            $object->$tagName = $element->nodeValue();
        }

        return $object;
    }

}
Run Code Online (Sandbox Code Playgroud)

然而,这似乎解决了这个问题,现在我的测试并没有真正做任何事情.我需要创建一个模拟DomDocument返回我在测试中测试的XML:

$object = new stdclass;
$object->foo = 'bar';

$mockDocument = $this->getMock('document')
                ->expects($this->once())
                ->method('saveXML')
                ->will(returnValue('<?xml verison="1.0"?><root><foo>bar</foo></root>'));

$serializer = new XMLSerializer($mockDocument);

$serializer->serialize($object);
Run Code Online (Sandbox Code Playgroud)

这有几个问题:

  1. 我根本没有测试该方法,我正在检查的是该方法返回的结果 $document->saveXML()
  2. 测试知道该方法的实现(它使用domdocument生成xml)
  3. 如果将类重写为使用simplexml或其他xml库,则测试将失败,即使它可能产生正确的结果

那我可以单独测试这段代码吗?它看起来像我不能..这种类型的依赖是否有一个名称无法模拟,因为它的行为基本上是被测试方法所必需的?

wor*_*oru 11

这是关于TDD的问题.TDD意味着首先编写测试.

DOMElement::createElement在编写实际的实现之前,我无法想象从模拟测试开始.你开始使用一个对象和期望的xml是很自然的.

另外,我不会称之为DOMElement依赖.这是您实施的私人细节.您永远不会将不同的实现传递DOMElement给构造函数,XMLSerializer因此不需要在构造函数中公开它.

测试也应作为文档.使用对象和预期的xml进行简单测试是可读的.每个人都可以阅读它,并确保你的班级正在做什么.将此与50行测试与模拟相比较(PhpUnit模拟是荒谬的冗长).

编辑:这是一篇关于它的好文章http://www.jmock.org/oopsla2004.pdf.简而言之,它指出除非你使用测试来驱动你的设计(找到接口),否则使用模拟没什么意义.

还有一个很好的规则

只有你自己的模拟类型

(在论文中提到)可以应用于你的例子.