比较XML以进行单元测试

今天早些时候,我的一个同事问我一个问题。他正在为序列化实用程序编写单元测试,需要将生成的xml与预期结果的手写xml文件进行比较。

By eye inspection, the xml seemed to be the same, but just in a different order. The elements were in a different order and the attributes were in a different order. So although order didn’t matter functionally, it eliminated the ability to do:docA.InnerXml.Equals(docB.InnerXml)

经过一番谷歌搜索,我发现那里有一些工具可以进行XML比较,但是它们充斥着太多的选项和功能。我不想学习新知识;我只是想看看xml的内容是否相同(无论顺序如何)。

因此,大约一个小时后,我想出了自己的版本,该版本没有学习曲线,也没有其他选择。它只有一个公共方法:AreEqualNoOrder。

似乎这种事情可能对许多其他人都有用,所以我决定将其发布。附带的控制台应用程序在比较器上运行一些测试,以更好地了解其功能。

using System.Xml;

namespace XMLComparer
{
    /// <summary>
    /// Compares two xml files for equality of content in any order.
    /// </summary>
    public class XmlComparer
    {
        /// <summary>
        /// Compares two xml documents for eqality as defined for this comparer.
        /// </summary>
        /// <param name="docA">The first xml document.</param>
        /// <param name="docB">the second xml document.</param>
        /// <returns>True if the documents are equal, otherwise false.</returns>
        public static bool AreEqualNoOrder(XmlDocument docA, XmlDocument docB)
        {
            return nodesAreEqualNoOrder(docA.FirstChild, docB.FirstChild);
        }

        /// <summary>
        /// Compares two nodes for equality as defined for this comparer. 
        /// </summary>
        /// <param name="nodeA">A node from the first document.</param>
        /// <param name="nodeB">a node from the second document.</param>
        /// <returns>True if the nodes are equal, otherwise false.</returns>
        private static bool nodesAreEqualNoOrder(XmlNode nodeA, XmlNode nodeB)
        {
            ///////////////
            // Compare Text
            ///////////////
            var textA = nodeA.Value;
            var textB = nodeB.Value;

            if (textA == null || textB == null)
            {
                // if either is null, then they should both be null.
                if (!(textA == null && textB == null))
                {
                    return false;
                }
            }
            else
            {
                // if they are not null, the text should be the same
                if (!textA.Trim().Equals(textB.Trim()))
                {
                    return false;
                }
            }

            /////////////////////
            // Compare Attributes
            /////////////////////
            var attributesA = nodeA.Attributes;
            var attributesB = nodeB.Attributes;

            if (attributesA == null || attributesB == null)
            {
                // if either is null, then they should both be null.
                if (!(attributesA == null && attributesB == null))
                {
                    return false;
                }
            }
            else
            {
                // if there are attributes, there should be the same number on A as on B.
                if (attributesA.Count != attributesB.Count)
                {
                    return false;
                }

                // check each attribute and value
                for (int i = 0; i < attributesA.Count; i++)
                {
                    var name = attributesA[i].Name;

                    // if nodeA has an attribute named x, then so should nodeB.
                    if (attributesB[name] == null)
                    {
                        return false;
                    }

                    // if nodeA and nodeB both have attributes named x, they should have the same value.
                    if (attributesB[name].Value != attributesA[name].Value)
                    {
                        return false;
                    }
                }
            }

            //////////////////////
            // Compare Child Nodes
            //////////////////////
            var childsA = nodeA.ChildNodes;
            var childsB = nodeB.ChildNodes;

            // the number of children of nodeA should be the same as the number of childeren of nodeB.
            if (childsA.Count != childsB.Count)
            {
                return false;
            }

            // every child of nodeA should have a matching child of nodeB, but not necessarily in the same order.
            while (childsA.Count > 0)
            {
                // look for a match in nodeB for the first child of nodeA
                var matchFound = false;
                for (int i = 0; i < childsB.Count; i++)
                {
                    if (nodesAreEqualNoOrder(childsA[0], childsB[i]))
                    {
                        // if a match is found in nodeB, remove the child in nodeA and the match in nodeB, then move on.
                        // this is important because if A had child nodes x and x and B had child nodes x and y,
                        // then both A nodes would match, but B would have a y that wasn't found.
                        matchFound = true;
                        childsA[0].ParentNode.RemoveChild(childsA[0]);
                        childsB[i].ParentNode.RemoveChild(childsB[i]);
                        break;
                    }
                }
                if (!matchFound)
                {
                    // if no match is found, the nodes ain't the same.
                    return false;
                }
            }

            // if no test failed, the nodes are equal.
            return true;
        }

    }
}