Pregunta Comparando XML en una prueba unitaria en Python


Tengo un objeto que puede compilarse a partir de una cadena XML y escribirse a sí mismo en una cadena XML. Me gustaría escribir una prueba unitaria para probar la ida y vuelta a través de XML, pero estoy teniendo problemas para comparar las dos versiones XML. El espacio en blanco y el orden de los atributos parecen ser los problemas. ¿Alguna sugerencia de cómo hacer esto? Esto está en Python, y estoy usando ElementTree (no es lo que realmente importa aquí ya que estoy tratando con XML en cadenas en este nivel).


32
2017-11-26 19:09


origen


Respuestas:


Primero normaliza 2 XML, luego puedes compararlos. He usado lo siguiente usando lxml

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

12
2017-11-26 19:35



Esta es una vieja pregunta, pero la aceptada La respuesta de Kozyarchuk no funciona para mí debido al orden de los atributos, y el solución minidom no funciona como-tampoco (ni idea por qué, no lo he depurado).

Esto es lo que finalmente se me ocurrió:

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)

Esto también produce una diferencia que puede ser útil en el caso de archivos xml grandes.


15
2017-08-14 23:05



Si el problema es solo el espacio en blanco y el orden de los atributos, y no tiene más construcciones que el texto y los elementos de los que preocuparse, puede analizar las cadenas mediante un analizador XML estándar y comparar los nodos manualmente. Aquí hay un ejemplo usando minidom, pero podrías escribir lo mismo en etree de forma bastante simple:

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

Si necesita una comparación de equivalencia más completa, que cubra las posibilidades de otros tipos de nodos, incluidos CDATA, PI, referencias de entidades, comentarios, doctypes, espacios de nombres, etc., podría utilizar el método DOM Level 3 Core isEqualNode. Ni minidom ni etree tienen eso, pero pxdom es una implementación que lo soporta:

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

(Es posible que desee cambiar algunas de las opciones de configuración de DOM en el análisis si necesita especificar si las referencias de entidad y las secciones de CDATA coinciden con sus equivalentes reemplazados).

Una forma un poco más indirecta de hacerlo sería analizar, luego volver a serializar a la forma canónica y hacer una comparación de cadenas. De nuevo, pxdom admite la opción LS de DOM Level 3 'canonical-form' que puede usar para hacer esto; una forma alternativa de usar la implementación minidom de stdlib es usar c14n. Sin embargo, debe tener las extensiones PyXML instaladas para esto, por lo que aún no puede hacerlo dentro de 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

7
2017-11-26 19:56



Utilizar xmldiff, una herramienta de python que descubre las diferencias entre dos archivos XML similares, de la misma manera que diff lo hace.


5
2017-11-26 19:19



¿Por qué estás examinando los datos XML en absoluto?

La forma de probar la serialización de objetos es crear una instancia del objeto, serializarlo, deserializarlo en un nuevo objeto y comparar los dos objetos. Cuando realice un cambio que interrumpe la serialización o la deserialización, esta prueba fallará.

Lo único que comprobará si los datos XML van a encontrar es si su serializador está emitiendo un superconjunto de lo que requiere el deserializador, y el deserializador ignora silenciosamente las cosas que no espera.

Por supuesto, si algo más va a consumir los datos serializados, ese es otro asunto. Pero en ese caso, debería estar pensando en establecer un esquema para el XML y validarlo.


2
2017-11-26 20:46



También tuve este problema y lo exploré hoy. los doctestcompare enfoque puede ser suficiente, pero encontré a través de Ian Bicking que está basado en formencode.doctest_xml_compare. Lo que parece ser ahora aquí. Como pueden ver, es una función bastante simple, a diferencia doctestcompare (Aunque supongo doctestcompare está recopilando todas las fallas y tal vez una verificación más sofisticada). De todos modos, copiar / importar xml_compare fuera de formencode puede ser una buena solución.


1
2017-11-18 07:05



El componente de Java dbUnit realiza muchas comparaciones con XML, por lo que puede resultar útil analizar su enfoque (especialmente para encontrar cualquier problema que ya hayan abordado).


0
2017-11-27 00:20



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)

Podrías ver un resultado como:

                u'date': u'2016-11-22T19:55:02',
                u'item2': u'MX-INV0007',
         -      u'item3': u'Payments',
         ?                  ^^^
         +      u'item3': u'OAYments',
         ?                  ^^^ +

0
2017-11-23 02:05