41

我有一个可以从 XML 字符串构建自身并将自身写入 XML 字符串的对象。我想编写一个单元测试来测试通过 XML 的往返,但是我在比较两个 XML 版本时遇到了麻烦。空格和属性顺序似乎是问题所在。关于如何做到这一点的任何建议?这是在 Python 中,我使用的是 ElementTree(这并不重要,因为我只是在这个级别处理字符串中的 XML)。

4

10 回答 10

17

首先规范化 2 个 XML,然后您可以比较它们。我使用 lxml 使用了以下内容

obj1 = objectify.fromstring(expect)
expect = etree.tostring(obj1)
obj2 = objectify.fromstring(xml)
result = etree.tostring(obj2)
self.assertEquals(expect, result)
于 2008-11-26T19:35:15.063 回答
16

这是一个老问题,但是由于属性顺序,接受的 Kozyarchuk 的答案对我不起作用,并且minidom 解决方案也不能按原样工作(不知道为什么,我没有调试它)。

这就是我最终想出的:

from doctest import Example
from lxml.doctestcompare import LXMLOutputChecker

class XmlTest(TestCase):
    def assertXmlEqual(self, got, want):
        checker = LXMLOutputChecker()
        if not checker.check_output(want, got, 0):
            message = checker.output_difference(Example("", want), got, 0)
            raise AssertionError(message)

这也会产生一个差异,在处理大型 xml 文件时会有所帮助。

于 2011-08-14T23:05:34.563 回答
7

如果问题实际上只是空格和属性顺序,并且除了文本和元素之外没有其他结构需要担心,则可以使用标准 XML 解析器解析字符串并手动比较节点。这是一个使用 minidom 的示例,但您可以非常简单地在 etree 中编写相同的内容:

def isEqualXML(a, b):
    da, db= minidom.parseString(a), minidom.parseString(b)
    return isEqualElement(da.documentElement, db.documentElement)

def isEqualElement(a, b):
    if a.tagName!=b.tagName:
        return False
    if sorted(a.attributes.items())!=sorted(b.attributes.items()):
        return False
    if len(a.childNodes)!=len(b.childNodes):
        return False
    for ac, bc in zip(a.childNodes, b.childNodes):
        if ac.nodeType!=bc.nodeType:
            return False
        if ac.nodeType==ac.TEXT_NODE and ac.data!=bc.data:
            return False
        if ac.nodeType==ac.ELEMENT_NODE and not isEqualElement(ac, bc):
            return False
    return True

如果您需要更彻底的等价比较,涵盖其他类型节点的可能性,包括 CDATA、PI、实体引用、注释、文档类型、命名空间等,您可以使用 DOM Level 3 Core 方法 isEqualNode。minidom 和 etree 都没有,但 pxdom 是一种支持它的实现:

def isEqualXML(a, b):
    da, db= pxdom.parseString(a), pxdom.parseString(a)
    return da.isEqualNode(db)

(如果您需要指定实体引用和 CDATA 部分是否与其替换的等效项匹配,您可能需要更改解析中的一些 DOMConfiguration 选项。)

一种稍微迂回的方法是解析,然后重新序列化为规范形式并进行字符串比较。pxdom 再次支持 DOM Level 3 LS 选项“canonical-form”,您可以使用它来执行此操作;使用 stdlib 的 minidom 实现的另一种方法是使用 c14n。但是,您必须为此安装 PyXML 扩展,所以您仍然不能在 stdlib 中完成它:

from xml.dom.ext import c14n

def isEqualXML(a, b):
    da, bd= minidom.parseString(a), minidom.parseString(b)
    a, b= c14n.Canonicalize(da), c14n.Canonicalize(db)
    return a==b
于 2008-11-26T19:56:19.030 回答
5

使用xmldiff,这是一个 python 工具,可以找出两个相似 XML 文件之间的差异,与 diff 的方法相同。

于 2008-11-26T19:19:11.303 回答
3

为什么要检查 XML 数据?

测试对象序列化的方法是创建对象的实例,序列化,反序列化成新的对象,比较两个对象。当您进行破坏序列化或反序列化的更改时,此测试将失败。

检查 XML 数据的唯一事情是,如果您的序列化器发出了反序列化器所需的超集,并且反序列化器会默默地忽略它不期望的东西。

当然,如果其他东西会消耗序列化的数据,那就是另一回事了。但在这种情况下,您应该考虑为 XML 建立一个模式并对其进行验证。

于 2008-11-26T20:46:45.700 回答
1

我也有这个问题,今天做了一些挖掘。该doctestcompare方法可能就足够了,但我通过Ian Bicking发现它基于formencode.doctest_xml_compare. 现在似乎在这里。正如你所看到的,这是一个非常简单的函数,不像doctestcompare(虽然我猜doctestcompare是收集所有的失败,也许是更复杂的检查)。无论如何复制/导入可能是一个很好的解决方案xml_compareformencode

于 2011-11-18T07:05:26.207 回答
0

Java 组件dbUnit进行了大量的 XML 比较,因此您可能会发现查看他们的方法很有用(尤其是发现他们可能已经解决的任何问题)。

于 2008-11-27T00:20:52.677 回答
0
def xml_to_json(self, xml):
    """Receive 1 lxml etree object and return a json string"""
    def recursive_dict(element):
        return (element.tag.split('}')[1],
                dict(map(recursive_dict, element.getchildren()),
                     **element.attrib))
    return json.dumps(dict([recursive_dict(xml)]),
                      default=lambda x: str(x))

def assertEqualXML(self, xml_real, xml_expected):
    """Receive 2 objectify objects and show a diff assert if exists."""
    xml_expected_str = json.loads(self.xml_to_json(xml_expected))
    xml_real_str = json.loads(self.xml_to_json(xml_real))
    self.maxDiff = None
    self.assertEqual(xml_real_str, xml_expected_str)

您可以看到如下输出:

                u'date': u'2016-11-22T19:55:02',
                u'item2': u'MX-INV0007',
         -      u'item3': u'Payments',
         ?                  ^^^
         +      u'item3': u'OAYments',
         ?                  ^^^ +
于 2016-11-23T02:05:34.400 回答
0

可以通过以下方式轻松完成minidom

class XmlTest(TestCase):
    def assertXmlEqual(self, got, want):
        return self.assertEqual(parseString(got).toxml(), parseString(want).toxml())
于 2018-09-27T15:15:07.747 回答
0

Stevoisiak 的解决方案

在我的情况下不适用于 python3。固定的:

from lxml.doctestcompare import LXMLOutputChecker, PARSE_XML

class XmlTest(TestCase):
def assertXmlEqual(self, got, want):
    checker = LXMLOutputChecker()
    if not checker.check_output(want.encode(), got.encode(), PARSE_XML):
        message = checker.output_difference(Example(b"", want.encode()), got.encode(), PARSE_XML)
        raise AssertionError(message)
于 2020-10-15T10:36:37.437 回答