Подтвердить что ты не робот

Сравнение XML в unit test в Python

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

4b9b3361

Ответ 1

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

obj1 = objectify.fromstring(expect)
expect = etree.tostring(obj1)
obj2 = objectify.fromstring(xml)
result = etree.tostring(obj2)
self.assertEquals(expect, result)

Ответ 2

Это старый вопрос, но принятый ответ Козярчука не работает для меня из-за порядка атрибутов, а minidom solution не работает как есть (не знаю, почему, я не отлаживал его).

Вот что я, наконец, придумал:

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 файлов.

Ответ 3

Если проблема - это просто порядок пробелов и атрибутов, и у вас нет других конструкций, кроме текста и элементов, о которых вы можете беспокоиться, вы можете проанализировать строки, используя стандартный синтаксический анализатор 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 method 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 option '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

Ответ 4

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

Ответ 5

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

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

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

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

Ответ 6

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

Ответ 7

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

Ответ 8

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',
         ?                  ^^^ +