共享自己的一个单元测试帮助类-UnitTestHelper
单元测试在开发过程中的重要性不言而喻,很多时候仅使用Assert.*来进行断言检查仍然是不够的,Assert只知道对与错,适合于自动化测试,对程序的调试却帮助有限。试着想像这么一种情形:我们写了一个数据库访问类MyInfoDA,针对数据库进行CURD操作,其中有一个方法GetInfoById返回一个实体MyInfo的实例,我们可以用Assert.IsNotNull简单的检查是否有实例返回,却无法了解MyInfo的内部结构是怎样的。最简单的办法或许就是Console.WriteLine挨个将MyInfo的内容Print出来,或者就是直接在IDE调试器里面查看对象内容。而我很厌烦这么做,于是就写了这么一个帮助类,感觉还是蛮有用途的,因此公布出来,供大家参考。
1
using System.Diagnostics;
2
using System.Reflection;
3
using System.Text;
4
5
namespace 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
using System.Diagnostics;2
using System.Reflection;3
using System.Text;4

5
namespace NHTSS.UnitTest6
{7
/// <summary>8
/// 单元测试的帮助类。9
/// </summary>10
public sealed class UnitTestHelper11
{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
else61
{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
try84
{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:TRACE100
//Trace.WriteLine(ex.Message);101
Console.WriteLine("<<ERROR>> " + ex.Message);102
}103
}104
else105
{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]
5
public 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]
26
public 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
}
/// <summary>2
/// 测试简单的数据实体。3
/// </summary>4
[TestFixture]5
public class TestBaseInfo6
{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]26
public class TestParametersDA27
{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. 这是本人在博客园的第一篇正式技术文章,严格说来也没啥技术,只是一点实用技巧,以前很少动手写东西,肤浅不足之处,欢迎拍砖。


浙公网安备 33010602011771号