当进行单元测试的时候, 在某些情况下, 我们没有办法或者是比较难制造一个参照对象和函数执行的结果进行对比; 或者当这个对象是一个复杂对象的时候, 我们需要对比这个对象中的每一个属性; 所以在开发的过程中写了这样一个测试对比的工具; 方便做自动化单元测试的时候使用;
配置文件
首先, 我们需要创建一系列的存储对比结果的路径; 以及一些配置参数; 我们可以将这些参数放在配置文件中; 可以使用一个配置类; 在初始化这个配置类的时候, 将配置信息赋值给这个配置类中的对应属性;
internal class Configuration { public static string Domain { get; internal set; } public static string UserName { get; internal set; } public static string Password { get; internal set; } public static string RootDir { get; internal set; } private static Configuration() { Domain = ConfigurationManager.AppSettings["Domain"]; UserName = ConfigurationManager.AppSettings["UserName"]; Password = ConfigurationManager.AppSettings["Password"]; RootDir = ConfigurationManager.AppSettings["RootDir"]; } }
在之后使用的时候, 就可以直接使用了, 当然需要在App.config文件中添加你需要的节点和数据, 使用如下
<add key="Domain" value="DomainName"/>
格式;
其次, 当程序中还需要另外一些复杂的配置文件的时候, 就需要使用配置文件了; 当然配置文件有很多种; 但是比较常用的当然还是Xml; 在Project中添加一个Xml文件; 并将这个文件的Properties中的Build Action属性设置为Embedded Resource; 这样就可以直接使用了, 而不去找他的物理路径;
internal class XmlConfigUtility { private static string CONFIG_FILE_NAME = "ArtCoder.XmlConfiguration.xml"; private static XmlDocument mXmlDoc; internal static XmlDocument GetConfigXmlDoc() { if (mXmlDoc == null) { using (StreamReader sr = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream(CONFIG_FILE_NAME))) { mXmlDoc = new XmlDocument(); mXmlDoc.LoadXml(sr.ReadToEnd()); } } return mXmlDoc; } }
在外围处理具体的拆分逻辑, 并且形成一个Info类, 方面以后使用;
对比逻辑
internal void CompareObject(object [] srcObjs, object[] dirObjs, string xmlFileName) { DirectoryInfo dirInfo = new DirectoryInfo(Configuration.RootDir); if (!dirInfo.Exists) { dirInfo = Directory.CreateDirectory(Configuration.RootDir); } FileStream fs = new FileStream(Path.Combine(dirInfo.FullName, xmlFileName + ".xml"), FileMode.OpenOrCreate, FileAccess.Write); XmlDocument xmlDoc = new XmlDocument(); XmlElement xmlRoot = xmlDoc.CreateElement(xmlFileName); XmlElement xmlEle = xmlDoc.CreateElement(xmlFileName); for (int i = 0; i < srcObjs.Length; i++) { object srcObj = srcObjs[i]; object dirObj = dirObjs[i]; CompareObjectInternal(srcObj, dirObj, xmlEle, xmlDoc); xmlRoot.AppendChild(xmlEle); } xmlDoc.AppendChild(xmlRoot); fs.Write(Encoding.UTF8.GetBytes(xmlDoc.OuterXml), 0, Encoding.UTF8.GetBytes(xmlDoc.OuterXml).Length); } private bool CompareObjectInternal(object srcObj, object dirObj, XmlElement xmlEle, XmlDocument xmlDoc) { bool flag = true; BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance; if (srcObj == null || dirObj == null) { return false; } foreach (PropertyInfo property in srcObj.GetType().GetProperties(bindingFlags)) { if (dirObj.GetType().GetProperty(property.Name, bindingFlags) != null) { bool eveFlag = false; XmlElement xmlPro = xmlDoc.CreateElement(property.Name); object srcValue = property.GetValue(srcObj, null); object dirValue = dirObj.GetType().GetProperty(property.Name, bindingFlags).GetValue(dirObj, null); XmlElement xmlSrc = xmlDoc.CreateElement("SrcValue"); xmlSrc.SetAttribute("Value", srcValue == null ? "Null" : srcValue.ToString()); xmlSrc.SetAttribute("Type", srcValue == null ? "Null" : srcValue.GetType().Name); XmlElement xmlDir = xmlDoc.CreateElement("DirValue"); xmlDir.SetAttribute("Value", dirValue == null ? "Null" : dirValue.ToString()); xmlDir.SetAttribute("Type", dirValue == null ? "Null" : dirValue.GetType().Name); xmlPro.AppendChild(xmlSrc); xmlPro.AppendChild(xmlDir); if (TypeHelper.IsBasicType(property.PropertyType)) { if (srcValue != null && dirValue != null && srcValue.GetType() == dirValue.GetType()) { eveFlag = srcValue.Equals(dirValue); } else if (srcValue == null && dirValue == null) { eveFlag = true; } else { eveFlag = false; } xmlPro.SetAttribute("Equals", eveFlag.ToString()); } else if (property.PropertyType.IsGenericType) { Type srcType = srcValue.GetType(); Type dirType = dirValue.GetType(); if (typeof(IDictionary).IsAssignableFrom(srcType) && typeof(IDictionary).IsAssignableFrom(dirType)) { if ((srcValue as IDictionary).Count != 0) { object keys = srcType.GetProperty("Keys").GetGetMethod().Invoke(srcValue, null); PropertyInfo item = srcType.GetProperty("Item"); var enumerator = keys.GetType().GetMethod("GetEnumerator").Invoke(keys, null); var moveNext = enumerator.GetType().GetMethod("MoveNext"); var current = enumerator.GetType().GetProperty("Current").GetGetMethod(); // Get all the values. while ((bool)moveNext.Invoke(enumerator, null)) { try { object key = current.Invoke(enumerator, null); object srcDicValue = item.GetValue(srcValue, new object[] { key }); object dirDicValue = dirType.GetProperty("Item").GetValue(dirValue, new object[] { key }); eveFlag = CompareObjectInternal(srcDicValue, dirDicValue, xmlPro, xmlDoc); } catch (Exception ex) { eveFlag = false; } } } else { xmlPro.SetAttribute("Equals", "True"); } } else if (typeof(IList).IsAssignableFrom(srcType) && typeof(IList).IsAssignableFrom(dirType)) { PropertyInfo srcItems = srcType.GetProperty("Item"); PropertyInfo dirItems = dirType.GetProperty("Item"); int srcCount = (srcValue as IList).Count; int dirCount = (srcValue as IList).Count; if (srcCount != 0) { for (int i = 0; i < srcCount; i++) { try { object srcDicValue = srcItems.GetValue(srcValue, new object[] { i }); object dirDicValue = dirItems.GetValue(dirValue, new object[] { i }); eveFlag = CompareObjectInternal(srcDicValue, dirDicValue, xmlPro, xmlDoc); } catch (Exception ex) { eveFlag = false; } } } else { xmlPro.SetAttribute("Equals", "True"); } } } else { eveFlag = CompareObjectInternal(srcValue, dirValue, xmlPro, xmlDoc); } xmlEle.AppendChild(xmlPro); if (!eveFlag && flag) { flag = false; } xmlEle.SetAttribute("Equals", flag.ToString()); } } return flag; }
首先可以选择对比的范围, 可以将一些对象的对比结果放在一个对比文件中; 也可以将每个对象放在一个文件中;
其次, 在对比对象属性时, 需要考虑到对象属性的类型; 如果是简单类型的话, 可以直接进行对比, 如果是集合类型就需要特殊处理; 如果是复杂对象, 则需要进一步对比复杂对象属性的属性;
在此我们讨论一下对于集合对象的对比;
对于Dictionary类型的集合对象;
Type srcType = srcValue.GetType(); Type dirType = dirValue.GetType(); if (typeof(IDictionary).IsAssignableFrom(srcType) && typeof(IDictionary).IsAssignableFrom(dirType)) { if ((srcValue as IDictionary).Count != 0) { object keys = srcType.GetProperty("Keys").GetGetMethod().Invoke(srcValue, null); PropertyInfo item = srcType.GetProperty("Item"); var enumerator = keys.GetType().GetMethod("GetEnumerator").Invoke(keys, null); var moveNext = enumerator.GetType().GetMethod("MoveNext"); var current = enumerator.GetType().GetProperty("Current").GetGetMethod(); // Get all the values. while ((bool)moveNext.Invoke(enumerator, null)) { try { object key = current.Invoke(enumerator, null); object srcDicValue = item.GetValue(srcValue, new object[] { key }); object dirDicValue = dirType.GetProperty("Item").GetValue(dirValue, new object[] { key }); eveFlag = CompareObjectInternal(srcDicValue, dirDicValue, xmlPro, xmlDoc); } catch (Exception ex) { eveFlag = false; } } } else { xmlPro.SetAttribute("Equals", "True"); } }
需要反射获取迭代器中的每一个对象, 然后分别进行对比;
如果是List类型的集合; 则需要
else if (typeof(IList).IsAssignableFrom(srcType) && typeof(IList).IsAssignableFrom(dirType)) { PropertyInfo srcItems = srcType.GetProperty("Item"); PropertyInfo dirItems = dirType.GetProperty("Item"); int srcCount = (srcValue as IList).Count; int dirCount = (srcValue as IList).Count; if (srcCount != 0) { for (int i = 0; i < srcCount; i++) { try { object srcDicValue = srcItems.GetValue(srcValue, new object[] { i }); object dirDicValue = dirItems.GetValue(dirValue, new object[] { i }); eveFlag = CompareObjectInternal(srcDicValue, dirDicValue, xmlPro, xmlDoc); } catch (Exception ex) { eveFlag = false; } } } else { xmlPro.SetAttribute("Equals", "True"); } }
同样的也需要反射获取;
此外位于对比结果; 如果子节点中有对比结果不一致的, 则上层节点的对比结果也是失败的;