Domanda

Ho un oggetto che può costruirsi da una stringa XML e scriverlo su una stringa XML. Mi piacerebbe scrivere un test unitario per testare l'attraversamento circolare in XML, ma ho problemi a confrontare le due versioni XML. Gli spazi bianchi e l'ordine degli attributi sembrano essere i problemi. Qualche suggerimento su come farlo? Questo è in Python e sto usando ElementTree (non è quello che conta davvero qui dato che ho a che fare con XML nelle stringhe a questo livello).

È stato utile?

Soluzione

Prima normalizza 2 XML, quindi puoi confrontarli. Ho usato quanto segue usando lxml

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

Altri suggerimenti

Questa è una vecchia domanda, ma la risposta di Kozyarchuk accettata non funziona per me a causa dell'ordine degli attributi, e la soluzione minidom non funziona così com'è (non ho idea del perché, non ho eseguito il debug).

Questo è quello che alla fine mi è venuta in mente:

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)

Questo produce anche un diff che può essere utile in caso di file XML di grandi dimensioni.

Se il problema riguarda davvero solo gli spazi bianchi e l'ordine degli attributi e non ci sono altri costrutti oltre al testo e agli elementi di cui preoccuparsi, è possibile analizzare le stringhe utilizzando un parser XML standard e confrontare manualmente i nodi. Ecco un esempio usando minidom, ma potresti scrivere lo stesso in etree semplicemente:

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

Se hai bisogno di un confronto di equivalenza più approfondito, che copra le possibilità di altri tipi di nodi tra cui CDATA, PI, riferimenti a entità, commenti, doctype, spazi dei nomi e così via, puoi usare il metodo Core Level 3 isEqualNode. Né minidom né etree lo hanno, ma pxdom è un'implementazione che lo supporta:

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

(Potresti voler modificare alcune delle opzioni di DOMConfiguration all'analisi se devi specificare se i riferimenti di entità e le sezioni CDATA corrispondono ai loro equivalenti sostituiti.)

Un modo leggermente più circolare di farlo sarebbe quello di analizzare, quindi ricerializzare in forma canonica e fare un confronto di stringhe. Ancora una volta pxdom supporta l'opzione LS livello DOM 3 & # 8216; forma canonica & # 8217; che potresti usare per fare questo; un modo alternativo di usare l'implementazione minidom di stdlib è usare c14n. Tuttavia è necessario disporre delle estensioni PyXML installate per questo, quindi non è ancora possibile farlo all'interno dello 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

Usa xmldiff , uno strumento python che identifica le differenze tra due file XML simili, allo stesso modo che diff lo fa.

Perché stai esaminando i dati XML?

Il modo per testare la serializzazione degli oggetti è creare un'istanza dell'oggetto, serializzarlo, deserializzarlo in un nuovo oggetto e confrontare i due oggetti. Quando si effettua una modifica che interrompe la serializzazione o la deserializzazione, questo test fallirà.

L'unica cosa che verifica i dati XML sta per trovare per te è se il tuo serializzatore sta emettendo un superset di ciò che il deserializzatore richiede e il deserializzatore ignora silenziosamente ciò che non si aspetta.

Ovviamente, se qualcos'altro consumerà i dati serializzati, è un'altra questione. Ma in quel caso, dovresti pensare di stabilire uno schema per l'XML e convalidarlo.

Ho anche avuto questo problema e ho fatto qualche ricerca intorno ad esso oggi. L'approccio doctestcompare può essere sufficiente, ma ho scoperto tramite Ian Bicking che si basa su formencode.doctest_xml_compare . Che ora sembra essere qui . Come puoi vedere, questa è una funzione abbastanza semplice, a differenza di doctestcompare (anche se immagino che doctestcompare stia raccogliendo tutti gli errori e magari un controllo più sofisticato). In ogni caso, copiare / importare xml_compare da formencode può essere una buona soluzione.

Il componente Java dbUnit fa molti confronti XML, quindi potresti trovare utile esaminare il loro approccio (specialmente per trovare i gotcha che potrebbero aver già affrontato).

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)

Potresti vedere un output come:

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

Può essere facilmente eseguito con minidom :

class XmlTest(TestCase):
    def assertXmlEqual(self, got, want):
        return self.assertEqual(parseString(got).toxml(), parseString(want).toxml())
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top