Сравнение XML в модульном тесте в Python

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

0

8 ответы

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

0
добавлено
xmldiff - GPL. Означает ли это, что я должен открыть исходный код моего сценария?
добавлено автор guettli, источник

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

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

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

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

0
добавлено
Да, что-то еще будет потреблять сериализованные данные. Я могу перейти к созданию схемы и ее проверке, но сейчас выполнение строкового сравнения xml достаточно хорошо.
добавлено автор Adam Endicott, источник

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

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

obj1 = objectify.fromstring(expect)
expect = etree.tostring(obj1)
obj2 = objectify.fromstring(xml)
result = etree.tostring(obj2)
self.assertEquals(expect, result)
0
добавлено
О человек, я пробовал это и думал, что атрибуты были заказаны по-разному, но я снова посмотрел, и на самом деле я просто отсутствовал в своем выпуске. Спасибо, что ударил меня по голове.
добавлено автор Adam Endicott, источник
Хех. Незначительная осторожность, etree не документирует никаких гарантий для сериализации атрибутов в любом конкретном порядке. По крайней мере, текущая реализация ElementTree на чистом Python делает для них sort (), но неясно, как вы можете положиться на это.
добавлено автор bobince, источник
Мой опыт работы с etree заключается в том, что он сериализует их в том же порядке, в котором они были первоначально записаны в документе.
добавлено автор Mark E. Haase, источник
Опасайтесь: сериализация может отличаться в зависимости от версии Python, особенно порядка атрибутов.
добавлено автор Stan, источник
Остерегайтесь интервала, который может быть сохранен. Пример: t = ET.tostring; е = ET.fromstring; t (f (' '))! = t (f (' ')) . Вниз, потому что я просто выстрелил в ногу с этим.
добавлено автор kjaquier, источник

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

0
добавлено

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

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

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.

0
добавлено
Я не уверен, в чем проблема; этот подход используется как для тестов Python 2, так и для Python 3:
добавлено автор Mikhail Korobov, источник
возможно, вам понадобится .decode ('utf8')
добавлено автор Mikhail Korobov, источник
У меня проблемы с работой в Python3 из-за проблем с кодировкой строк, независимо от того, какая комбинация bytes() , bytearray() или encode ('utf -8 ') Я использовал. Я не уверен, что это проблема в библиотеке или я просто что-то пропустил, но это не сработало для меня.
добавлено автор Aaron D, источник

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

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',
         ?                  ^^^ +
0
добавлено
Python
Python
7 654 участник(ов)

Уютный чат для профессионалов, занимающихся поиском питоньих мудростей. Как не получить бан: https://t.me/ru_python/577926

Python beginners
Python beginners
4 449 участник(ов)

Вопросы про Python для чайников. Cпам и троллинг неприемлем. Не злоупотребляйте стикерами. Частозадаваемые вопросы: https://github.com/ru-python-beginners/faq/blob/master/README.md Статистика тут: https://grstats.me/chat/x4qym2k5uvfkr3al6at7

pro.python
pro.python
1 090 участник(ов)

Сообщество разработчиков под Python Создатель: @rodgelius

Rude Python
Rude Python
971 участник(ов)

Python без „девочек”, здесь матерятся и унижают Django. Not gay friendly. Правила: t.me/rudepython/114107 @rudepython | t.me/rudepython

rupython
rupython
509 участник(ов)

Группа создана с целью оперативного получения ответов на возникающие вопросы по разработке на яп python, смежные темы, а также человеческого общения. Приветствую!

Python-programming
Python-programming
266 участник(ов)

Чат группы вконтакте https://vk.com/python_community