26/4/2005
URL: http://www.zdnet.com.cn/developer/code/story/0,3800066897,39371757,00.htm
微软将C#语言及CLR(通用语言运行时即Common Language Runtime)作为业界标准的举动已经促使许多基于.NET平台的开放源代码项目的产生。
其中一个引人注目的项目就是NUnit,它提供了对所有.NET语言的单元测试框架。在下面的文章中,我将为您展示Nunit是如何有效的对代码进行单元测试的。
单元测试
即使你爱上了编写代码,你还是会发现对代码进行彻底测试并不是那么吸引人。???然而,对开发人员来说,在代码开发阶段捕获并修正问题,远比等着别人在QA列表中找出问题要更为划算。因为后者需要开发人员重新对代码进行检查。这就是单元测试背后的思想。
单元测试涉及到对应用程序类的公共接口测试。这就是说,你需要测试这些类以确定他们能同预期的那样表现优异。这包括了验证结果是否像预期那样被返回,以及程序是否提供了正确的异常处理。NUnit是完成这些工作的非常好的工具。它为.NET平台提供了灵活的测试框架,并为运行测试提供了易于使用的操作界面。
在NUnit.org Web站点可以免费获得Windows和Mono环境下的NUnit(在撰写本文时,我正在使用一台Windows XP计算机,而文中的示例则是SUSE 9.2 Linux的Mono环境下测试的)。现在让我们来进一步了解实际使用中的NUnit框架。
使用NUnit
NUnit体系结构非常明了。你可以在几分钟内上手并运行示例程序,而不是几小时。一旦将其安装到系统上,该框架就被放置在nunit.framework.dll文件中。如果要开发基于NUnit的代码,那么请保证这一文件能够为相关项目所访问。或许需要通过Visual Studio .NET添加一条reference,或者你也可以将其放到应用程序的bin目录中。接下来,你必须在代码中添加一条到NUnit名字空间的reference(或利用NUnit类的完整路径)。接下来的C#代码片断显示了这些内容:
usingNUnit.Framework;
以下是等价VB.NET代码
Imports NUnit.Framework
现在你就可以使用NUnit.Framework名字空间中的类了。但在深入代码编写之前,还是来检查一下相关的NUnit属性。
NUnit属性
NUnit框架使用了许多自定义属性以定义代码中所使用的NUnit元素。这些属性需要放在代码元素之前。以下是属性列表:
ExpectedException属性包括了该方法所能返回的异常;
Setup属性标志了在实际单元测试运行前所执行的一段代码;
TearDown属性标志了所有测试运行后所需要运行的一段代码;
Test是一个单独的单元测试。它应当被放在一个方法之前,由此标示这是一个测试;
Ignore可以被放在一个单元测试之前,由此禁止对此单元的测试。Nunit将忽略任何标志有此属性的代码测试;
TestFixture标志测试单元的集合。它被放在类声明之前,由此标示该类包含有测试。
NUnit将首先执行Setup,然后是单独测试(即individual tests,且如果没有指定手动运行则按在代码中出现的顺序执行),最后是运行TearDown。NUnit不执行任何标记有Ignore的代码。此外,NUnit提供了多种用于单元测试的assertion(你同样可以自己开发自定义方法),这些assertion可以通过Assert类的相关方法获得:
AreEqual判断两个对象是否相等的。两个对象在二者皆为null或具有相同值时被认为相等;
AreSame判断两个对象是否参考同一对象;
IsFalse判断条件是否为false;
IsNull判断对象是否为null;
IsNotNull判断对象非null;
IsTrue判断条件是否为true。
你可以使用这些assertion对代码进行单元测试。向大家展示其功能的最好办法就是给出一段代码示例。如下的C#类将会通过NUnit进行测试:
using System;
using NUnit.Framework;
namespace UnitTestingSampleClass {
public class ExampleClass {
public string ConvertToUpper(string val) {
return val.ToUpper();
}
public string ConvertToLower(string val) {
return val.ToLower();
} } }
这是一个非常简单的类,它提供了两种方法。第一种方法将一个字符串转化为大写,第二个则完成相反功能,将字符串转化为小写。你可以有自己的类,但你需要开发相应的NUnit类对其进行测试。NUnit类如下:
using System;
using NUnit.Framework;
using UnitTestingSampleClass;
namespace UnitTestingSample {
[TestFixture]
public class TestClass {
private string test1 = null;
private string test2 = null;
ExampleClasstestObj = null;
[SetUp]
public void init() {
testObj = new ExampleClass();
test1 = "techrepublic.com";
test2 = TECHREPUBLIC.COM";
}
[Test]
public void TestA() {
Assert.AreEqual(testObj.ConvertToLower(test2), test2.ToLower());
}
[Test]
public void TestB() {
Assert.AreEqual(testObj.ConvertToUpper(test1), test1.ToUpper());
}
[Test]
[ExpectedException(typeof(NullReferenceException))]
public void NullTest() {
Assert.AreEqual(testObj.ConvertToLower(null),"");
}
[Test]
[Ignore("Not Used")]
public void TestC() { }
[Test]
public void TestD() {
Assert.AreEqual(testObj.ConvertToLower(test1), test1.ToUpper());
}
[TearDown]
public void End() {
test1 = null;
test2 = null;
testObj = null;
} } }
该类包括了五种测试:
TestA:ConvertToLower方法的测试办法是传递字符串值并将其同String类ToLower方法的返回值进行比较。如果相等,那么Assert.AreEqual将返回True(测试通过)。否则返回False即表示测试未通过。
TestB:ConvertToUpper方法的测试办法则是传递字符串值并将其同String类ToUpper方法的返回值进行比较。如果二者相等,那么Assert.AreEqual将返回True(测试通过)。否则返回False即表示测试未通过。
NullTest:测试将null值传递给ConvertToLower方法是否会使代码正确的抛出Exception。如果二者相等或指定Exception被抛出(NullPointerException),那么Assert.IsEqua测试将通过(返回True);否则返回False表示测试失败。
TestC:该段测试并没有代码,因此被标记会忽略。
TestD:由于该测试将一个对ToLower方法的调用同一个被转换为大写的字符串进行比较,因此该测试将失败。这给出了测试失败的一个示例。
此外,方法init将在测试(Setup)开始之前运行,并且一旦测试结束,那么用于清理现场的结束(TearDown)方法将被调用。NUnit提供了一个图形接口来运行测试。你可以通过如下命令行调用该界面对示例代码进行测试:
nunit-gui "c:/UnitTestingSample.exe" /run
Assembly将会被放在我的系统的C驱动根目录下,因此在你的系统上路径可能有所不同。你应当在传递给nunit-gui的第一个参数中对assembly指定完整路径。如果你更喜欢命令行,你可以使用如下的命令行版本功能:
nunit-console "c:/UnitTestingSample.exe"
在我的系统上,这一命令将生成如下输出:
NUnit version 2.2.0
Copyright (C) 2002-2003 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov,
Charlie Poole..
Copyright (C) 2000-2003 Philip Craig.
All Rights Reserved.
OS Version: Microsoft Windows NT 5.1.2600.0 .NET Version: 1.1.4322.573
....N.F
Tests run: 4, Failures: 1, Not run: 1, Time: 0.1093876
Failures:
1) TestUnitTestingConsole.TestClass.TestD :
String lengths are both 11.
Strings differ at index 0.
expected:<"techrepublic.com">
but was:<"TECHREPUBLIC.COM">
-----------^
at TestUnitTestingConsole.TestClass.TestD() in c:/TestClass.cs:line 55
Tests not run:
1) TestUnitTestingConsole.TestClass.TestC : Not Used
每一种方法都提供了数量巨大的命令行选项,你可以通过/help来查看这些选项。等价的VB.NET代码及首先被测试的类代码如下:
Public Class VBNetTestClass
Public Function ConvertToUpper(ByValval As String) As String
Return val.ToUpper()
End Function
Public Function ConvertToLower(ByValval As String) As String
Return val.ToLower()
End Function
End Class
实际用于测试的NUnit类代码:
Imports NUnit.Framework
Imports UnitTestClassVBNet
<TestFixture()> Public Class Class1
Private test1 As String
Private test2 As String
Private testObj As VBNetTestClass
<SetUp()> Public Sub Init()
testObj = New VBNetTestClass
test1 = "techrepublic.com"
test2 = "TECHREPUBLIC.COM"
End Sub
<Test()> Public Sub TestA()
Assert.AreEqual(testObj.ConvertToLower(test2), test2.ToLower())
End Sub
<Test()> Public Sub TestB()
Assert.AreEqual(testObj.ConvertToUpper(test1), test1.ToUpper())
End Sub
<Test(), Ignore("sample ignore")> Public Sub TestC()
End Sub
<Test()> Public Sub TestD()
Assert.AreEqual(testObj.ConvertToLower(test1), test1.ToUpper())
End Sub
<Test(), ExpectedException(GetType(NullReferenceException))> Public Sub NullTest()
Assert.AreEqual(testObj.ConvertToLower(Nothing), "")
End Sub
<TearDown()> Public Sub CleanUp()
test1 = Nothing
test2 = Nothing
testObj = Nothing
End Sub
End Class
越早捕获运行时错误越好
单元测试将使得开发人员尽早捕获运行时错误,这将比等待QA测试人员发现这些错误提前很多时间。此外,这种方法还能使代码在你脑海中有印象时就可以对其进行修正。在编写实际代码之前就创建测试过程的原理同样正在获得开发人员的认可,但这一问题将是以后讨论的主题。
Tony Patton的职业生涯是从一名应用程序员开始的,他通过获取Java、VB、Lotus和XML相关认证极大丰富了他的编程知识。