共享自己的一个单元测试帮助类-UnitTestHelper
单元测试在开发过程中的重要性不言而喻,很多时候仅使用Assert.*来进行断言检查仍然是不够的,Assert只知道对与错,适合于自动化测试,对程序的调试却帮助有限。试着想像这么一种情形:我们写了一个数据库访问类MyInfoDA,针对数据库进行CURD操作,其中有一个方法GetInfoById返回一个实体MyInfo的实例,我们可以用Assert.IsNotNull简单的检查是否有实例返回,却无法了解MyInfo的内部结构是怎样的。最简单的办法或许就是Console.WriteLine挨个将MyInfo的内容Print出来,或者就是直接在IDE调试器里面查看对象内容。而我很厌烦这么做,于是就写了这么一个帮助类,感觉还是蛮有用途的,因此公布出来,供大家参考。
1using System.Diagnostics;
2using System.Reflection;
3using System.Text;
4
5namespace NHTSS.UnitTest
6{
7 /// <summary>
8 /// 单元测试的帮助类。
9 /// </summary>
10 public sealed class UnitTestHelper
11 {
12 private UnitTestHelper()
13 {
14 }
15
16 /// <summary>
17 /// 格式化输出一个对象的所有属性值。
18 /// </summary>
19 /// <param name="obj">格式化输出一个对象的所有属性值。</param>
20 /// <returns>对象名属性值。</returns>
21 public static string ObjectProperty2String(object obj)
22 {
23 return ObjectProperty2String(obj, new Type[] {});
24 }
25
26 /// <summary>
27 /// 格式化输出一个对象的所有属性值。
28 /// </summary>
29 /// <param name="obj">格式化输出一个对象的所有属性值。</param>
30 /// <param name="exposeItems">特别需要暴露的类型。</param>
31 /// <returns>对象名属性值。</returns>
32 /// <remarks>没有跨层次概念,如果需要,应将链结构的类型都放置在exposeItems中。</remarks>
33 public static string ObjectProperty2String(object obj, Type[] exposeItems)
34 {
35 int tmp = 0;
36 return ObjectProperty2String(obj, exposeItems, ref tmp);
37 }
38
39 private static string ObjectProperty2String(object obj, Type[] exposeItems, ref int layer)
40 {
41 layer++;
42
43 StringBuilder sb = new StringBuilder();
44 if (obj != null)
45 {
46 Type objType = obj.GetType();
47 sb.Append(objType.Name).Append(" ->");
48 MemberInfo[] members = objType.GetMembers();
49 if ((objType.IsValueType) && (!objType.IsPrimitive))
50 {
51 // value type use Field.
52 foreach (MemberInfo m in members)
53 {
54 if (m.MemberType == MemberTypes.Field)
55 {
56 InvokePropertyOrField(sb, obj, objType, exposeItems, ref layer, m, BindingFlags.Default | BindingFlags.GetField);
57 }
58 }
59 }
60 else
61 {
62 // reference type use Property.
63 foreach (MemberInfo m in members)
64 {
65 if (m.MemberType == MemberTypes.Property)
66 {
67 InvokePropertyOrField(sb, obj, objType, exposeItems, ref layer, m, BindingFlags.Default | BindingFlags.GetProperty);
68 }
69 }
70 }
71 }
72
73 layer--;
74
75 return sb.ToString();
76 }
77
78 private static void InvokePropertyOrField(StringBuilder sb, object obj, Type objType, Type[] exposeItems, ref int layer, MemberInfo m, BindingFlags flags)
79 {
80 object val = null;
81 if (m.Name == "Item")
82 {
83 try
84 {
85 if (m.DeclaringType.Equals(m.ReflectedType))
86 {
87 // assume it is a index indicator [Array].
88 int count = (int) objType.InvokeMember("Count", BindingFlags.Default | BindingFlags.GetProperty, null, obj, new object[] {});
89 for (int k = 0; k < count; k++)
90 {
91 val = objType.InvokeMember("get_Item", BindingFlags.Default | BindingFlags.InvokeMethod, null, obj, new object[] {k});
92 sb.Append("\n").Append("\t".PadRight(layer, '\t')).Append(m.Name).Append("[").Append(k).Append("]=<").Append(val).Append(">; ");
93 ExposeDetail(sb, val, exposeItems, ref layer);
94 }
95 }
96 }
97 catch (Exception ex)
98 {
99 // use compiler swtich /d:TRACE
100 //Trace.WriteLine(ex.Message);
101 Console.WriteLine("<<ERROR>> " + ex.Message);
102 }
103 }
104 else
105 {
106 val = objType.InvokeMember(m.Name, flags, null, obj, new object[] {});
107 sb.Append("\n").Append("\t".PadRight(layer, '\t')).Append(m.Name).Append("=<").Append(val).Append(">; ");
108 ExposeDetail(sb, val, exposeItems, ref layer);
109 }
110 }
111
112 // recursion function.
113 private static void ExposeDetail(StringBuilder sb, object val, Type[] exposeItems, ref int layer)
114 {
115 if (sb == null || val == null || exposeItems == null)
116 {
117 return;
118 }
119 if (!ContainType(val.GetType(), exposeItems))
120 {
121 return;
122 }
123 string str = ObjectProperty2String(val, exposeItems, ref layer);
124 sb.Append("\r\n").Append("\t".PadRight(layer, '\t')).Append(str);
125 }
126
127 // estimate Type.
128 private static bool ContainType(Type valType, Type[] exposeItems)
129 {
130 if (exposeItems == null)
131 {
132 return false;
133 }
134 int count = exposeItems.Length;
135 for (int k = 0; k < count; k++)
136 {
137 if (valType == exposeItems[k])
138 {
139 return true;
140 }
141 }
142 return false;
143 }
144 }
145}
146
2using System.Reflection;
3using System.Text;
4
5namespace NHTSS.UnitTest
6{
7 /// <summary>
8 /// 单元测试的帮助类。
9 /// </summary>
10 public sealed class UnitTestHelper
11 {
12 private UnitTestHelper()
13 {
14 }
15
16 /// <summary>
17 /// 格式化输出一个对象的所有属性值。
18 /// </summary>
19 /// <param name="obj">格式化输出一个对象的所有属性值。</param>
20 /// <returns>对象名属性值。</returns>
21 public static string ObjectProperty2String(object obj)
22 {
23 return ObjectProperty2String(obj, new Type[] {});
24 }
25
26 /// <summary>
27 /// 格式化输出一个对象的所有属性值。
28 /// </summary>
29 /// <param name="obj">格式化输出一个对象的所有属性值。</param>
30 /// <param name="exposeItems">特别需要暴露的类型。</param>
31 /// <returns>对象名属性值。</returns>
32 /// <remarks>没有跨层次概念,如果需要,应将链结构的类型都放置在exposeItems中。</remarks>
33 public static string ObjectProperty2String(object obj, Type[] exposeItems)
34 {
35 int tmp = 0;
36 return ObjectProperty2String(obj, exposeItems, ref tmp);
37 }
38
39 private static string ObjectProperty2String(object obj, Type[] exposeItems, ref int layer)
40 {
41 layer++;
42
43 StringBuilder sb = new StringBuilder();
44 if (obj != null)
45 {
46 Type objType = obj.GetType();
47 sb.Append(objType.Name).Append(" ->");
48 MemberInfo[] members = objType.GetMembers();
49 if ((objType.IsValueType) && (!objType.IsPrimitive))
50 {
51 // value type use Field.
52 foreach (MemberInfo m in members)
53 {
54 if (m.MemberType == MemberTypes.Field)
55 {
56 InvokePropertyOrField(sb, obj, objType, exposeItems, ref layer, m, BindingFlags.Default | BindingFlags.GetField);
57 }
58 }
59 }
60 else
61 {
62 // reference type use Property.
63 foreach (MemberInfo m in members)
64 {
65 if (m.MemberType == MemberTypes.Property)
66 {
67 InvokePropertyOrField(sb, obj, objType, exposeItems, ref layer, m, BindingFlags.Default | BindingFlags.GetProperty);
68 }
69 }
70 }
71 }
72
73 layer--;
74
75 return sb.ToString();
76 }
77
78 private static void InvokePropertyOrField(StringBuilder sb, object obj, Type objType, Type[] exposeItems, ref int layer, MemberInfo m, BindingFlags flags)
79 {
80 object val = null;
81 if (m.Name == "Item")
82 {
83 try
84 {
85 if (m.DeclaringType.Equals(m.ReflectedType))
86 {
87 // assume it is a index indicator [Array].
88 int count = (int) objType.InvokeMember("Count", BindingFlags.Default | BindingFlags.GetProperty, null, obj, new object[] {});
89 for (int k = 0; k < count; k++)
90 {
91 val = objType.InvokeMember("get_Item", BindingFlags.Default | BindingFlags.InvokeMethod, null, obj, new object[] {k});
92 sb.Append("\n").Append("\t".PadRight(layer, '\t')).Append(m.Name).Append("[").Append(k).Append("]=<").Append(val).Append(">; ");
93 ExposeDetail(sb, val, exposeItems, ref layer);
94 }
95 }
96 }
97 catch (Exception ex)
98 {
99 // use compiler swtich /d:TRACE
100 //Trace.WriteLine(ex.Message);
101 Console.WriteLine("<<ERROR>> " + ex.Message);
102 }
103 }
104 else
105 {
106 val = objType.InvokeMember(m.Name, flags, null, obj, new object[] {});
107 sb.Append("\n").Append("\t".PadRight(layer, '\t')).Append(m.Name).Append("=<").Append(val).Append(">; ");
108 ExposeDetail(sb, val, exposeItems, ref layer);
109 }
110 }
111
112 // recursion function.
113 private static void ExposeDetail(StringBuilder sb, object val, Type[] exposeItems, ref int layer)
114 {
115 if (sb == null || val == null || exposeItems == null)
116 {
117 return;
118 }
119 if (!ContainType(val.GetType(), exposeItems))
120 {
121 return;
122 }
123 string str = ObjectProperty2String(val, exposeItems, ref layer);
124 sb.Append("\r\n").Append("\t".PadRight(layer, '\t')).Append(str);
125 }
126
127 // estimate Type.
128 private static bool ContainType(Type valType, Type[] exposeItems)
129 {
130 if (exposeItems == null)
131 {
132 return false;
133 }
134 int count = exposeItems.Length;
135 for (int k = 0; k < count; k++)
136 {
137 if (valType == exposeItems[k])
138 {
139 return true;
140 }
141 }
142 return false;
143 }
144 }
145}
146
说明一下,其实思路很简单的,就是通过GetType()获得该对象的元数据,然后通过反射输出该对象的值域(Field和Property)。对集合对象,或者特别想要了解的属性或字段通过递归方式处理。
UnitTestHelper 提供了两个静态重载方法ObjectProperty2String,也比较好理解。下面给出两个简单的例子:
1/// <summary>
2/// 测试简单的数据实体。
3/// </summary>
4[TestFixture]
5public class TestBaseInfo
6{
7 [Test]
8 public void TestBuildInfoMock()
9 {
10 //InfoMock 是个简单的测试实体对象。
11 InfoMock mock = new InfoMock();
12 mock.Name = "Mock";
13 mock.Address = "Paradise";
14 mock.Age = "No.1";
15 mock.Company = "Chiron";
16 mock.Credit = "IBM + Google + Microsoft";
17 mock.InfoId = 0;
18 Console.Out.WriteLine(UnitTestHelper.ObjectProperty2String(mock));
19 }
20}
21
22/// <summary>
23/// 测试ParametersDA。
24/// </summary>
25[TestFixture]
26public class TestParametersDA
27{
28 [Test]
29 public void TestGetAllParameters()
30 {
31 // ParameterCollection是一个数据实体集合。
32 ParameterCollection coll = ParametersDA.Instance.GetAllParams();
33 Console.Out.WriteLine(UnitTestHelper.ObjectProperty2String(coll, new Type[]{ typeof(ParameterInfo) }));
34 }
35}
2/// 测试简单的数据实体。
3/// </summary>
4[TestFixture]
5public class TestBaseInfo
6{
7 [Test]
8 public void TestBuildInfoMock()
9 {
10 //InfoMock 是个简单的测试实体对象。
11 InfoMock mock = new InfoMock();
12 mock.Name = "Mock";
13 mock.Address = "Paradise";
14 mock.Age = "No.1";
15 mock.Company = "Chiron";
16 mock.Credit = "IBM + Google + Microsoft";
17 mock.InfoId = 0;
18 Console.Out.WriteLine(UnitTestHelper.ObjectProperty2String(mock));
19 }
20}
21
22/// <summary>
23/// 测试ParametersDA。
24/// </summary>
25[TestFixture]
26public class TestParametersDA
27{
28 [Test]
29 public void TestGetAllParameters()
30 {
31 // ParameterCollection是一个数据实体集合。
32 ParameterCollection coll = ParametersDA.Instance.GetAllParams();
33 Console.Out.WriteLine(UnitTestHelper.ObjectProperty2String(coll, new Type[]{ typeof(ParameterInfo) }));
34 }
35}
P.S. 这是本人在博客园的第一篇正式技术文章,严格说来也没啥技术,只是一点实用技巧,以前很少动手写东西,肤浅不足之处,欢迎拍砖。