反射是.Net、Java等托管语言区别于传统语言(C++,Delphi等)的一大特性,通过反射我们可以动态的创建类,调用方法等。考虑了一下NUnit的功能,觉得利用这个特性也可以做一个出来,于是就动手做了个练习,基本达到了效果,模拟NUnit提供框架如下:
//=======================================
UnitDll (模拟nunit.framework)
[AttributeUsage(AttributeTargets.Class)]
public class TestFixtureAttribute : System.Attribute
{
public TestFixtureAttribute()
{
}
}
[AttributeUsage(AttributeTargets.Method)]
public class Test : System.Attribute
{
public Test()
{
}
}
// 以上为两个Attribute,标识要测试的类与方法
/// <summary>
/// Assert的异常类,用来抛出Assert异常
/// </summary>
public class VAssertException : System.ApplicationException
{
private string m_expstr = "";
public VAssertException()
{
}
public VAssertException(string err)
{
m_expstr = err;
}
public string ExceptionStr
{
get { return m_expstr; }
set { m_expstr = value; }
}
}
// 以上为异常类,Assert失败的时候扔出异常
/// <summary>
/// 用于单元测试的Assert类
/// </summary>
public class Assert
{
public Assert()
{
}
public static void AreEqual(object expected, object actual)
{
if (expected != actual)
{
string str = "expected is:"+expected+" but actual is:"+actual;
VAssertException e = new VAssertException(str);
throw e;
}
}
}
// 以上为提供测试方法类,提供AreEqual,模拟NUnit提供的Assert类
//===========================================================
TestDll : 要进行单元测试的类,使用上面提供的UnitDll
/// <summary>
/// // 要进行单元测试的类
/// </summary>
[TestFixture] // 标识为要进行测试的类
public class VUnitTest
{
public VUnitTest()
{
System.Console.WriteLine("Now the Unit Test Class create");
}
[Test]
public void TestMethod()
{
System.Console.WriteLine("This is a unit test method");
Assert.AreEqual(1,2); // 使用UnitDll提供的Assert类进行单元测试
}
//==================================
UnitTest.exe 模拟NUnit.exe,提供的一个win form,主要以treeview的方式展现。
界面的代码比较简单就不提供了,提供几段关键的反射与调用的代码:
private void LoadDll()
{
this.treeView1.Nodes.Clear();
rootnode = this.treeView1.Nodes.Add(openFileDialog1.FileName);
a = Assembly.LoadFrom(openFileDialog1.FileName);
Type [] types = a.GetTypes();
foreach (Type type in types)
{
object [] attributes = type.GetCustomAttributes(false);
foreach (object obj in attributes)
{
// 得到了带有测试注明信息的类
if (obj.GetType() == typeof(TestFixtureAttribute))
{
// 增加一个类名
TreeNode node = rootnode.Nodes.Add(type.Name);
MethodInfo [] methods = type.GetMethods();
foreach (MethodInfo method in methods)
{
object [] methodattributes = method.GetCustomAttributes(false);
foreach (object objmehtod in methodattributes)
{
if (objmehtod.GetType() == typeof(Test))
{
node.Nodes.Add(method.Name);
}
}
}
}
}
}
this.treeView1.ExpandAll();
}
// 上面的代码比较简单,利用反射得到需要进行测试的类与方法,并展现在一个treeview中
object obj = Activator.CreateInstance(type);
MethodInfo method = type.GetMethod(this.treeView1.SelectedNode.Text);
if (method == null)
{
MessageBox.Show("方法不匹配");
return;
}
object [] parameters = new object[0];
method.Invoke(obj,parameters);
this.listBox1.Items.Insert(0,"ok");
SetLabel(true);
}
catch(System.Reflection.TargetInvocationException ex)
{
string str = ex.ToString();
Exception exc = ex.InnerException;
if (exc.GetType() == typeof(VAssertException))
{
VAssertException objex = (VAssertException)exc;
string sstr = objex.ExceptionStr;
this.listBox1.Items.Insert(0,sstr);
this.listBox1.Items.Insert(0,exc.StackTrace);
this.listBox1.Items.Insert(0,ex.StackTrace);
SetLabel(false);
}
// 上面的代码相当于NUnit界面中的run,也是比较核心的一段
// 主要就是通过创建要测试的类(CreateInstance),然后调用Invoke
// 判断是否断言失败(是否有自定义的异常扔出),有的话测试失败,把失败信息显示在界面即可
//======================================================
小结: 基本可以达到NUnit的效果,但是业务与界面的代码混在一起,没有很好的隔离,
比较好的方法是把反射这块封起来,提供一个接口给界面反应即可。
请大家多提意见,谢谢