Сравнение XML в модульном тесте в Python - PullRequest
33 голосов
/ 26 ноября 2008

У меня есть объект, который может построить себя из строки XML и записать себя в строку XML. Я хотел бы написать модульный тест для проверки обхода через XML, но у меня возникают проблемы при сравнении двух версий XML. Пробелы и порядок атрибутов, кажется, проблемы. Любые предложения о том, как это сделать? Это на Python, и я использую ElementTree (здесь это не имеет значения, так как я просто имею дело с XML в строках на этом уровне).

Ответы [ 9 ]

15 голосов
/ 15 августа 2011

Это старый вопрос, но принятый ответ Козарчука не работает для меня из-за порядка следования атрибутов, и решение 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)

Это также создает diff, который может быть полезен в случае больших XML-файлов.

13 голосов
/ 26 ноября 2008

Сначала нормализуйте 2 XML, затем вы можете сравнить их. Я использовал следующее, используя lxml

obj1 = objectify.fromstring(expect)
expect = etree.tostring(obj1)
obj2 = objectify.fromstring(xml)
result = etree.tostring(obj2)
self.assertEquals(expect, result)
7 голосов
/ 26 ноября 2008

Если проблема в действительности заключается только в пробелах и порядке атрибутов, и вам не о чем беспокоиться, кроме текста и элементов, вы можете проанализировать строки с помощью стандартного анализатора 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 уровня 3 isEqualNode. Ни у minidom, ни у etree этого нет, но pxdom - это одна из реализаций, которая поддерживает это:

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

(Вы можете изменить некоторые параметры DOMConfiguration при разборе, если вам нужно указать, соответствуют ли ссылки на сущности и разделы CDATA их замененным эквивалентам.)

Немного более окольный способ сделать это - проанализировать, затем повторно сериализовать в каноническую форму и выполнить сравнение строк. Снова pxdom поддерживает опцию DOM Level 3 LS «canonical-form», которую вы можете использовать для этого; альтернативный способ использования реализации минидома в stdlib - использование 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
5 голосов
/ 26 ноября 2008

Используйте xmldiff , инструмент python, который вычисляет различия между двумя похожими XML-файлами так же, как это делает diff.

2 голосов
/ 26 ноября 2008

Почему вы вообще изучаете данные XML?

Способ проверки сериализации объекта заключается в создании экземпляра объекта, его сериализации, десериализации его в новый объект и сравнении двух объектов. При внесении изменения, которое нарушает сериализацию или десериализацию, этот тест не пройден.

Единственная вещь, которую проверяет данные XML, найдет для вас, если ваш сериализатор излучает расширенный набор того, что требуется десериализатору, и десериализатор молча игнорирует то, чего не ожидает.

Конечно, если что-то еще будет потреблять сериализованные данные, это другое дело. Но в этом случае вам следует подумать о создании схемы для XML и ее проверке.

1 голос
/ 18 ноября 2011

У меня тоже была эта проблема, и я сегодня немного покопался в ней. Подход doctestcompare может быть достаточным, но я обнаружил с помощью Ian Bicking , что он основан на formencode.doctest_xml_compare. Который, кажется, теперь здесь . Как вы можете видеть, это довольно простая функция, в отличие от doctestcompare (хотя я думаю, doctestcompare собирает все ошибки и, возможно, более сложную проверку). В любом случае, копирование / импорт xml_compare из formencode может быть хорошим решением.

0 голосов
/ 27 сентября 2018

Это легко сделать с помощью minidom:

class XmlTest(TestCase):
    def assertXmlEqual(self, got, want):
        return self.assertEqual(parseString(got).toxml(), parseString(want).toxml())
0 голосов
/ 23 ноября 2016
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',
         ?                  ^^^ +
0 голосов
/ 27 ноября 2008

Компонент Java dbUnit выполняет множество сравнений XML, поэтому вам может быть полезно взглянуть на их подход (особенно для поиска ошибок, к которым они, возможно, уже обращались).

...