texto parrafo para niƱos ejemplo corto comparativo .net xml linq unit-testing

.net - parrafo - La mejor manera de comparar objetos XElement



parrafo comparativo wikipedia (6)

Comencé por el mismo camino que @llasarov, pero tampoco me gustó el uso de cuerdas. Descubrí XElement.DeepEquals () aquí, así que encontrar la pregunta me ayudó.

Pude ver que podría ser difícil si su prueba devuelve una estructura XML masiva, pero en mi opinión, esto no debería hacerse; la prueba debería verificar una estructura lo más pequeña posible.

Digamos que tiene un método que espera devolver un elemento que se parece a <Test Sample="Value" /> . Puede usar los constructores XElement y XAttribute para construir su valor esperado con bastante facilidad, como esto:

[TestMethod()] public void MyXmlMethodTest() { // Use XElement API to build expected element. XElement expected = new XElement("Test", new XAttribute("Sample", "Value")); // Call the method being tested. XElement actual = MyXmlMethod(); // Assert using XNode.DeepEquals Assert.IsTrue(XNode.DeepEquals(expected, actual)); }

Incluso si hay un puñado de elementos y atributos, esto es manejable y consistente.

en una prueba unitaria estoy comparando un objeto XElement con el que espero. El método que utilizo es invocar .ToString() en el objeto XElement y compararlo con un valor de cadena codificado. Este método resultó ser bastante incómodo ya que siempre tengo que prestar atención al formato en la cadena.

Revisé el método XElement.DeepEquals () pero por alguna razón no ayuda.

¿Alguien tiene una idea de cuál es el mejor método que debería usar?


Depende de lo que estés probando. ¿Necesita verificar que el XML es igual o equivalente?

Sospecho que esto último, en cuyo caso debería consultar sobre el elemento xelq utilizando xlinq y afirmar que tiene los elementos y atributos necesarios.

Al final del día baja lo que se requiere. Por ejemplo

<element att=''xxxx''> <sub /> </element>

y

<element att=''zzz'' />

puede ser equivalente si no te importa <sub /> or att


Encontré este excelente artículo útil. Contiene un ejemplo de código que implementa una alternativa a XNode.DeepEquals que normaliza los árboles XML antes de la comparación, lo que hace que el contenido no semántico sea irrelevante.

Para ilustrar, la implementación de XNode.DeepEquals devuelve false para estos documentos semánticamente equivalentes:

XElement root1 = XElement.Parse("<Root a=''1'' b=''2''><Child>1</Child></Root>"); XElement root2 = XElement.Parse("<Root b=''2'' a=''1''><Child>1</Child></Root>");

Sin embargo, al utilizar la implementación de DeepEqualsWithNormalization del artículo, obtendrá el valor true porque el orden de los atributos no se considera significativo. Esta implementación se incluye a continuación.

using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Xml; using System.Xml.Linq; using System.Xml.Schema; public static class MyExtensions { public static string ToStringAlignAttributes(this XDocument document) { XmlWriterSettings settings = new XmlWriterSettings(); settings.Indent = true; settings.OmitXmlDeclaration = true; settings.NewLineOnAttributes = true; StringBuilder stringBuilder = new StringBuilder(); using (XmlWriter xmlWriter = XmlWriter.Create(stringBuilder, settings)) document.WriteTo(xmlWriter); return stringBuilder.ToString(); } } class Program { private static class Xsi { public static XNamespace xsi = "http://www.w3.org/2001/XMLSchema-instance"; public static XName schemaLocation = xsi + "schemaLocation"; public static XName noNamespaceSchemaLocation = xsi + "noNamespaceSchemaLocation"; } public static XDocument Normalize(XDocument source, XmlSchemaSet schema) { bool havePSVI = false; // validate, throw errors, add PSVI information if (schema != null) { source.Validate(schema, null, true); havePSVI = true; } return new XDocument( source.Declaration, source.Nodes().Select(n => { // Remove comments, processing instructions, and text nodes that are // children of XDocument. Only white space text nodes are allowed as // children of a document, so we can remove all text nodes. if (n is XComment || n is XProcessingInstruction || n is XText) return null; XElement e = n as XElement; if (e != null) return NormalizeElement(e, havePSVI); return n; } ) ); } public static bool DeepEqualsWithNormalization(XDocument doc1, XDocument doc2, XmlSchemaSet schemaSet) { XDocument d1 = Normalize(doc1, schemaSet); XDocument d2 = Normalize(doc2, schemaSet); return XNode.DeepEquals(d1, d2); } private static IEnumerable<XAttribute> NormalizeAttributes(XElement element, bool havePSVI) { return element.Attributes() .Where(a => !a.IsNamespaceDeclaration && a.Name != Xsi.schemaLocation && a.Name != Xsi.noNamespaceSchemaLocation) .OrderBy(a => a.Name.NamespaceName) .ThenBy(a => a.Name.LocalName) .Select( a => { if (havePSVI) { var dt = a.GetSchemaInfo().SchemaType.TypeCode; switch (dt) { case XmlTypeCode.Boolean: return new XAttribute(a.Name, (bool)a); case XmlTypeCode.DateTime: return new XAttribute(a.Name, (DateTime)a); case XmlTypeCode.Decimal: return new XAttribute(a.Name, (decimal)a); case XmlTypeCode.Double: return new XAttribute(a.Name, (double)a); case XmlTypeCode.Float: return new XAttribute(a.Name, (float)a); case XmlTypeCode.HexBinary: case XmlTypeCode.Language: return new XAttribute(a.Name, ((string)a).ToLower()); } } return a; } ); } private static XNode NormalizeNode(XNode node, bool havePSVI) { // trim comments and processing instructions from normalized tree if (node is XComment || node is XProcessingInstruction) return null; XElement e = node as XElement; if (e != null) return NormalizeElement(e, havePSVI); // Only thing left is XCData and XText, so clone them return node; } private static XElement NormalizeElement(XElement element, bool havePSVI) { if (havePSVI) { var dt = element.GetSchemaInfo(); switch (dt.SchemaType.TypeCode) { case XmlTypeCode.Boolean: return new XElement(element.Name, NormalizeAttributes(element, havePSVI), (bool)element); case XmlTypeCode.DateTime: return new XElement(element.Name, NormalizeAttributes(element, havePSVI), (DateTime)element); case XmlTypeCode.Decimal: return new XElement(element.Name, NormalizeAttributes(element, havePSVI), (decimal)element); case XmlTypeCode.Double: return new XElement(element.Name, NormalizeAttributes(element, havePSVI), (double)element); case XmlTypeCode.Float: return new XElement(element.Name, NormalizeAttributes(element, havePSVI), (float)element); case XmlTypeCode.HexBinary: case XmlTypeCode.Language: return new XElement(element.Name, NormalizeAttributes(element, havePSVI), ((string)element).ToLower()); default: return new XElement(element.Name, NormalizeAttributes(element, havePSVI), element.Nodes().Select(n => NormalizeNode(n, havePSVI)) ); } } else { return new XElement(element.Name, NormalizeAttributes(element, havePSVI), element.Nodes().Select(n => NormalizeNode(n, havePSVI)) ); } } }


Para una prueba de unidad, el método más simple es que XElement también analice la cadena esperada.

string expected = "<some XML>"; XElement result = systemUnderTest.DoSomething(); Assert.Equal(XElement.Parse(expected).ToString(), result.ToString());


Tuve un problema al comparar XElements para la igualdad donde uno de los elementos tenía nodos secundarios que tenían etiquetas de cierre automático pero el otro tenía las etiquetas de apertura y cierre, por ejemplo, [blah /] vs [blah] [/ blah]

La función de profundos iguales era, por supuesto, informar que eran diferentes, así que necesitaba una función de normalización. Terminé usando una variante de lo que se publica en este blog (por "marianor"):

http://weblogs.asp.net/marianor/archive/2009/01/02/easy-way-to-compare-two-xmls-for-equality.aspx

Un cambio menor es que uso la función deep equals después de la normalización (en lugar de la comparación de cadenas) y también agregué lógica para tratar los elementos que contienen el texto vacío igual que los elementos vacíos (para resolver el problema mencionado anteriormente). El resultado está abajo.

private bool CompareXml(string xml) { var a = Normalize(currentElement); var b = Normalize(newElement); return XElement.DeepEquals(a, b); } private static XElement Normalize(XElement element) { if (element.HasElements) { return new XElement(element.Name, element.Attributes().Where(a => a.Name.Namespace == XNamespace.Xmlns) .OrderBy(a => a.Name.ToString()),element.Elements().OrderBy(a => a.Name.ToString()) .Select(e => Normalize(e))); } if (element.IsEmpty || string.IsNullOrEmpty(element.Value)) { return new XElement(element.Name, element.Attributes() .OrderBy(a => a.Name.ToString())); } return new XElement(element.Name, element.Attributes() .OrderBy(a => a.Name.ToString()), element.Value); }


Un próximo paso que puede ayudar: una normalización que se deshace de CUALQUIER pedido. A veces, el orden de los elementos no importa en absoluto (piense en Colecciones en lugar de en Listas o Arrays).

Este se basa en el anterior (por RobJohnson) pero también ordena elementos según su "contenido", utiliza el número de atributos, los valores de atributo y el valor del elemento Xml en sí.

static XElement NormalizeWithoutAnyOrder( XElement element ) { if( element.HasElements ) { return new XElement( element.Name, element.Attributes().OrderBy( a => a.Name.ToString() ), element.Elements() .OrderBy( a => a.Name.ToString() ) .Select( e => NormalizeWithoutAnyOrder( e ) ) .OrderBy( e => e.Attributes().Count() ) .OrderBy( e => e.Attributes() .Select( a => a.Value ) .Concatenate("/u0001") ) .ThenBy( e => e.Value ) ); } if( element.IsEmpty || string.IsNullOrEmpty( element.Value ) ) { return new XElement( element.Name, element.Attributes() .OrderBy( a => a.Name.ToString() ) ); } return new XElement( element.Name, element.Attributes() .OrderBy( a => a.Name.ToString() ), element.Value ); }

El método de extensión IEnumerable.Concatenate es el mismo que el método string.Join .