如何使用PHPUnit比较相似的XML文件?

6

假设我想比较两个DOMDocument对象。它们具有相同的内容,但顺序和格式可能不同。例如,第一个输出此XML:

<responses>
    <response id="12">
        <foo>bar</foo>


 <lorem>ipsum</lorem>
           <sit>dolor</sit>

    </response></responses>

其他输出结果:

<responses>
<response id="12">

            <lorem>ipsum</lorem><sit>dolor</sit>
        <foo>bar</foo>
                            </response>
</responses>

如您所见,它们包含相同的XML结构,但某些元素可能按不同顺序排列,格式完全随机。

如果我执行:

$this->assertEquals();

这个测试肯定会失败。我不仅想测试XML结构,还要测试内容。
有什么好的想法吗?

如何先移除所有空格,再比较它们的SHA1哈希值呢? - Mr Coder
6个回答

4

1
虽然这理论上回答了问题,但最好在此处包含答案的基本部分,并提供参考链接。 - Nanne
1
实际上,该链接现在已经失效,使得这个答案对今天来说毫无用处。 - nIcO
@nIcO修复了损坏的链接。 - Richard Knop
$this->assertXmlStringEqualsXmlString('<foo><bar/></foo>', $xmlString); - dusan

3
这是哪个版本的PHPUnit?我非常确定最近的版本都支持DomDocument比较。
简短版:使用$doc->preserveWhiteSpace设置来移除空格,然后使用$doc->C14N()来删除注释并获得一个可以比较的字符串。
好的,这里有一个你可以尝试的脚本,请注意EOD;行不能有任何前导或尾随空格。
    $x1 = <<<EOD
<responses>
    <response id="12">
        <foo>bar</foo>

 <lorem>ipsum</lorem>
           <sit>dolor</sit>
        <!--This is a comment -->

    </response></responses>
EOD;

$x2 = <<<EOD
<responses>
<response id="12">

            <lorem>ipsum</lorem><sit>dolor</sit>
        <foo>bar</foo>
        <!--This is another comment -->
                            </response>
</responses>
EOD;

// 下一个块是同一文件的一部分,我只是在这里加入了这个格式中断,以便StackOverflow语法高亮系统不会崩溃。

$USE_C14N = true; // Try false, just to see the difference.

$d1 = new DOMDocument(1.0);
$d2 = new DOMDocument(1.0);

$d1->preserveWhiteSpace = false;
$d2->preserveWhiteSpace = false;

$d1->formatOutput = false; // Only useful for "pretty" output with saveXML()
$d2->formatOutput = false; // Only useful for "pretty" output with saveXML()

$d1->loadXML($x1); // Must be done AFTER preserveWhiteSpace and formatOutput are set
$d2->loadXML($x2); // Must be done AFTER preserveWhiteSpace and formatOutput are set   

if($USE_C14N){
    $s1 = $d1->C14N(true, false);
    $s2 = $d2->C14N(true, false);
} else {
    $s1 = $d1->saveXML();
    $s2 = $d2->saveXML();
}

echo $s1 . "\n";
echo $s2 . "\n";

使用$USE_C14N=true;输出

<responses><response id="12"><foo>bar</foo><lorem>ipsum</lorem><sit>dolor</sit></response></responses>
<responses><response id="12"><lorem>ipsum</lorem><sit>dolor</sit><foo>bar</foo></response></responses>

使用$USE_C14N=false;输出

<?xml version="1.0"?>
<responses><response id="12"><foo>bar</foo><lorem>ipsum</lorem><sit>dolor</sit><!--This is a comment --></response></responses>

<?xml version="1.0"?>
<responses><response id="12"><lorem>ipsum</lorem><sit>dolor</sit><foo>bar</foo><!--This is another comment --></response></responses>

请注意,$doc->C14N() 可能会比较慢,但我认为删除注释是可取的。此外,所有这些都假定您的XML中的空格不重要,可能有一些用例并不适用于此假设...


不确定是否已更改,但是 DOMDocument$version 参数是字符串而不是浮点数。使用严格模式的人会得到以下提示:DOMDocument::__construct() expects parameter 1 to be string, float given - NeverEndingQueue

1

我建议您将XML转换为DOMDocuments,然后使用assertEquals进行比较。PHPUnit已经支持此功能 - 但这可能还不足以满足您的所有需求。

您还可以重新格式化文档并重新加载它们,参见PHP XML how to output nice format

$doc->preserveWhiteSpace = false;
$doc->formatOutput = true;

另一个想法是按标签名称对子元素进行排序 - 不知道以前是否有人这样做过。

0

我一直在尝试一些这里提出的概念,并想着我也可以发布我的最终结果。我想做的事情之一是比较两个节点或两个文档的结果。(从技术上讲,只要将类似文档的第一个子节点与另一个进行比较,就可以比较任何一个)

基本上,如果我发送一个DomDocument,它会使用$clone->loadXml($obj->saveXml)克隆它,但如果发送的是一个节点,则会执行$clone->importNode($obj)。if语句的顺序变得重要,因为DomDocument也是DomNode的实例。

/**
 * @param \DOMDocument|\DOMNode $n1
 * @param \DOMDocument|\DOMNode $n2
 * @return bool
 * @throws \Exception for invalid data
 */
function compareNode($n1, $n2)
{
    $nd1 = new \DOMDocument('1.0', "UTF-8");
    if ($n1 instanceof \DOMDocument) {
        $nd1 = $n1->cloneNode(true);
        $nd1->preserveWhiteSpace = false;
        $nd1->formatOutput = false;
        $nd1->loadXML($n1->saveXML());
    } elseif ($n1 instanceof \DOMNode) {
        $nd1->preserveWhiteSpace = false;
        $nd1->formatOutput = false;
        $nd1->importNode($n1);
    } else {
        throw new \Exception(__METHOD__ . " node 1 is invalid");
    }

    $nd2 = new \DOMDocument('1.0', "UTF-8");
    if ($n2 instanceof \DOMDocument) {
        $nd2 = $n2->cloneNode(true);
        $nd2->preserveWhiteSpace = false;
        $nd2->formatOutput = false;
        $nd2->loadXML($n2->saveXML());
    } elseif ($n1 instanceof \DOMNode) {
        $nd2->preserveWhiteSpace = false;
        $nd2->formatOutput = false;
        $nd2->importNode($n2);
    } else {
        throw new \Exception(__METHOD__ . " node 2 is invalid");
    }

    return ($nd1->C14N(true, false) == $nd2->C14N(true, false));
}

0
你可以使用PHPUnit的assertXmlFileEqualsXmlFile()、assertXmlStringEqualsXmlFile()和assertXmlStringEqualsXmlString()函数;然而,它们并不提供有关不同之处的信息,它们只会让测试失败。
Failed asserting that two DOM documents are equal.

因此,您可能希望使用PHP的XMLDiff PECL扩展,或编写自己的递归比较函数。如果时间很重要,我建议不要使用DOM,而是使用SimpleXML,因为它具有更简单的API。


0
请使用以下断言:
$this->assertXmlStringEqualsXmlString($expected, $actual);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接