【问题标题】:How to test a class that generates XML internally如何测试在内部生成 XML 的类
【发布时间】:2013-09-25 16:08:18
【问题描述】:

目前我正在测试一个基于值对象生成 XML、通过 HTTP 发送 XML 并将 XML 响应解析回第二个值对象的类。在这种情况下,我想测试生成的 XML 和基于给定 XML 解析的值对象。

类看起来像:

class MyClient
{
    public function send(RequestValues $request)
    {
        $document = $this->generateMessage($request);
        $response = $this->request($document);

        return $this->parseResponse($response);
    }

    protected function generateMessage(RequestValues $request)
    {
        $document = new DomDocument;
        // Do stuff with $request

        return $document;
    }

    public function request(DomDocument $document)
    {
        $client = $this->getHttpClient();
        $client->setRawBody($document->saveXml());
        // Configure client

        return $client->send();
    }

    public function parseResponse(Response $response)
    {
        $parameters = new ResponseValues;

        $document = new DomDocument;
        $document->loadXml($response->getBody());

        // Fill in $parameters
        return $parameters;
    }
}

我想测试两件事:

  1. 给定某个RequestValues 参数,生成的XML 必须看起来像$string
  2. 给定某个 XML 响应值(HTTP 客户端将被模拟),ResponseValues 必须等于 $object

我现在正在为#1 编写测试,但我认为我只能通过回调来实现这一点。然而,当测试失败时,回调并没有给我非常有用的信息。只有这条消息:

断言 DOMDocument Object () 被指定回调接受失败。

测试看起来像这样:

public function testRequestContainsValidXml()
{
    $client  = $this->getMock('MyClient', array('request'));

    $message = '';
    $client->expects($this->once())
           ->method('request')
           ->with($this->callback(function($object) use ($message) {
                return
                    ($object instanceof DomDocument)
                 && ($object->saveXml() === $message);
            }));

    $request = new DirectoryRequest;
    $client->send($request);
}

问题是:我怎样才能改进测试,以便可以进行正常的字符串比较?我很想让 phpunit 说“字符串 X 不等于 Y”,这极大地简化了调试。

附言。这个类的完整代码可以在 GitHub 上找到。当然,上面的例子是一个简化版本。这是实际的课程:https://github.com/juriansluiman/SlmIdealPayment/blob/master/src/SlmIdealPayment/Client/StandardClient.php#L58

PPS。如果必须更改代码才能对其进行测试,那不是问题。我只想保持公共 API 不变(即调用 ResponseValues send(RequestValues $request)

【问题讨论】:

  • 到底是什么问题?是不是比较$object->saveXml() === $message失败了?
  • @silkfire 如果 $message 是正确的,它不会失败而只是通过。但是,无法查看预期字符串和实际字符串之间的差异。您无法通过单元测试以任何方式进行调试,这可以通过例如$this->assertEquals($string1, $string2)。因此,单元测试可以帮助我理解代码中的错误,它是一个大黑匣子,如果您不小心遇到错误,它并没有真正的帮助。
  • 所以问题是(也用粗体表示)我如何改进测试。我可以让它通过,但问题是当它没有通过时从 phpunit 获取有意义的消息。
  • 有可供 PHP 使用的 diff 类,您可以使用它们来查看逐个字符的区别;那么你可以在单元测试中调用那个 diff 函数吗?
  • 比如FineDiff 确实不错;在这里测试他们利用课程的工具:raymondhill.net/finediff/viewdiff-ex.php

标签: php unit-testing mocking phpunit


【解决方案1】:

You class MyClient 暗示它是一个门面。要在 UnitTests 的上下文中测试外观,通常首先需要测试作为该外观的协作者的所有单元。

正如您通常模拟协作者而不是被测单元一样,您的测试设置看起来错误,因为您模拟的是 MyClient 被测单元而不是协作者。

例如:

您想测试MyClient::parseResponse() 方法是否返回预期的ResponseValues 对象。因此,您可以模拟 Response,因为它在这种情况下是协作者。

您可能还想模拟其他协作者 (ResponseValues),但是您不能,因为它是一个无法注入的隐藏依赖项(您可以通过向 MyClient 注入一个可以控制创建这样的ResponseValues)。

测试MyClient::parseResponse() 的内容太多了:您可以通过Response 模拟注入fixture XML,然后在返回值上运行您的断言。

为了测试请求是否包含有效的 XML (testRequestContainsValidXml()),我认为也不应该以那种复杂的形式进行。听起来您在这里有一个 HTTP 客户端,您应该只测试它是否可以工作,而不关心它是否也可以与 XML 一起工作,因为如果它可以工作,那么它也可以与 XML 一起工作。您测试有效 XML 的原因不是因为您想测试客户端。因此,请将该测试排除在客户端单元测试之外。

顺便说一句,字符串的断言。是:

$this->assertSame($expected, $actual);

Phpunit 会显示字符串之间的差异。其他一些程序员/测试人员在比较 XML 时也应用了一些 XML 规范化,以使差异更具可读性。如果您的 XML 处理无关紧要的空白没有问题,您可能会发现像 PHP XML how to output nice format 和相关的问答。

我希望我能稍微指出您面临的问题案例,并扩大您对问题的看法。我认为您在批判性地质疑您当前的模拟使用方面走得最远,对于我给出的示例,我已将模拟完全减少为提供协作者,而不是进行断言。断言只在被测单元上进行,这里是示例MyClient

【讨论】:

    猜你喜欢
    • 2021-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多