[英]Comparing XML in a unit test in Python
我有一個對象,它可以從一個 XML 字符串構建自己,並將自己寫出一個 XML 字符串。 我想編寫一個單元測試來測試通過 XML 的往返,但是我在比較兩個 XML 版本時遇到了麻煩。 空格和屬性順序似乎是問題所在。 關於如何做到這一點的任何建議? 這是在 Python 中,我使用的是 ElementTree(這在這里並不重要,因為我只是在這個級別處理字符串中的 XML)。
首先規范化2個XML,然后你可以比較它們。 我使用 lxml 使用了以下內容
obj1 = objectify.fromstring(expect)
expect = etree.tostring(obj1)
obj2 = objectify.fromstring(xml)
result = etree.tostring(obj2)
self.assertEquals(expect, result)
這是一個老問題,但是由於屬性順序,公認的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 文件的情況下可能會有所幫助。
如果問題真的只是空格和屬性順序,並且除了文本和元素之外您沒有其他結構需要擔心,您可以使用標准 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
使用xmldiff ,這是一個 Python 工具,可以找出兩個相似的 XML 文件之間的差異,與 diff 的方法相同。
為什么要檢查 XML 數據?
測試對象序列化的方法是創建對象的一個實例,將其序列化,反序列化為一個新的對象,然后比較兩個對象。 當您做出破壞序列化或反序列化的更改時,此測試將失敗。
檢查 XML 數據的唯一方法是您的序列化器是否發出了反序列化器所需內容的超集,而反序列化器會默默地忽略它不期望的內容。
當然,如果其他東西會消耗序列化數據,那就是另一回事了。 但在這種情況下,您應該考慮為 XML 建立模式並對其進行驗證。
我也有這個問題,今天做了一些挖掘。 doctestcompare
方法可能就足夠了,但我通過Ian Bicking發現它基於formencode.doctest_xml_compare
。 現在似乎在這里。 正如您所看到的,這是一個非常簡單的函數,與doctestcompare
不同(盡管我猜doctestcompare
正在收集所有失敗,並且可能進行更復雜的檢查)。 反正復制/進口xml_compare
出formencode
可能是一個很好的解決方案。
Java 組件dbUnit
了大量 XML 比較,因此您可能會發現查看他們的方法很有用(尤其是找出他們可能已經解決的任何問題)。
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',
? ^^^ +
使用minidom
可以輕松完成:
class XmlTest(TestCase):
def assertXmlEqual(self, got, want):
return self.assertEqual(parseString(got).toxml(), parseString(want).toxml())
在我的情況下不適用於 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)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.