单元测试中NUnit和DotNetMock的使用调研报告
单元测试是指开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试用于判断某个特定条件(或者场景)下某个特定函数的行为。单元测试不但会使我们的工作完成得更轻松,而且会令我们的设计变得更好,甚至大大减少我们花在调试上面的时间。
进行单元测试,目前的C++, Fortran,Ruby,Smalltalk, C#, Java, C都是一样,都有自己的测试框架,这些框架都可以从网上免费下载,本人选择了C#所对应的NUnit测试框架和DotNetMock框架来进行调研。
对于NUnit测试框架,目前支持.NET的各个版本架构(.NET1.0、.NET1.1、.NET2.0、.NET3.0、.NET3.5),对于.NET1.0、.NET1.1, 对应有NUnit-2.4.7-net-1.1等一系列版本,对于.NET2.0、.NET3.0、.NET3.5,架构 对应有NUnit-2.4.7-net-2.0等一系列版本,而目前DotNetMock的最高版本是DotNetMock-0.8.0,只能支持.NET1.0、.NET1.1架构,其对应.NET2.0、.NET3.0、.NET3.5架构的版本正在开发中。以上两种测试框架都可以从网上免费下载。
首先来介绍下NUnit框架,该框架提供了一系列方法来进行单元测试,并提供了图形化界面来进行测试操作,使用起来十分方便,对NUnit的研究,本人使用的环境是包含最新.NET3.5架构的VS2008集成环境和NUnit-2.4.7-net-2.0测试架构。
我写了一个十分简单的方法来进行测试,该方法是以一个整形数组为参数,返回该数组中的最大数,为了演示测试效果,本人写的方法中故意留有一些bug, 进而检验NUnit的工作效果。待测试方法如下:
下面开始在NUnit测试框架写测试方法,如下:
打开NUnit框架提供的图形化程序,如图
将上面编写的测试代码编译后,打开NUnit项目,如图
该图形化程序明确的表示出了标有TestFixture属性的类TestLargest和标有Test属性的方法TestMethod,选中要测试的方法,点击Run,如图
可以看出,图形化界面醒目的以红色标出测试失败,报错信息是:TestLargest.TestMethod: Expected: 9 But was: 2147483647, 很明显,我的方法中对变量max的初始值设置有误。我修改了方法,代码如下:
选中的就是我做的修改,将MaxValue属性修改成了MinValue属性,重新编译后再次运行NUnit图形化界面程序,显示为绿色进度条,运行成功!
当然,只靠一条断言语句Assert.AreEqual(9, Cmp.Largest(new int[] { 8, 9, 7 }))显然不保险,我决定再写一条断言语句,如下
编译后,再次运行图形化界面
测试架构再次报错:TestLargest.TestMethod: Expected: 9 But was: 8
仔细阅读方法代码:发现index < list.Length – 1边界值有误,应该为index < list.Length,修改过后的代码如下:
编译后再次运行图形化界面,进度条为绿色,成功!
从上述的案例可以看到,用NUnit测试框架来进行单元测试的编写十分方便快捷,该框架除了AreEquals方法外,还提供了IsNull, AreSame, IsTrue, Fail等方法
介绍过NUnit测试架构后,我再介绍下DotNetMock测试架构,单元测试的目标是一次只验证一个方法,但是倘若某个方法以来于其他一些难以操控的东西,比如网络、数据库等,将会比较难以控制,这时候,一种测试模式可以帮助我们:mock对象,Mock对象也就是对象在调试期的替代品。在.NET中,有多款mock对象的框架供选择,我调研了一种比较著名的框架DotNetMock,该框架目前只支持.NET1.0,.NET1.1框架,所以我使用的环境是集成.NET1.1框架的VS2003和NUnit-2.4.7-net-1.1测试框架和DotNetMock-0.8.0测试框架。
为了演示DotNetMock的使用,我编写了AccessController类,代码如下
该代码描述了一个日志接口,定义了设置日志名称的方法声明,在AccessController类中得以应用。但是只使用普通NUnit测试架构时,我只能对CanAccess方法进行测试,并不能测试是否正确给logger设置了它的名字,这就是mock对象框架能够帮上忙的地方,它的一大特色就是使得管理预期行为变得特别容易。我需要创建一个扩展了MockObject类和ILogger接口的MockLogger类来辅助完成这个任务。该类代码如下:
该类中定义了一个DotNetMock提供的ExpectationValue类型的字段,并提供了设置期望值和实际值的属性和方法。然后写测试方法如下
该方法既可以测试CanAccess方法的返回值,又可以测试是否设置日志名称为AccessControl,把上述代码编译后运行NUnit图形化程序,测试成功!
如果我希望设置日志名为Max的话,我代码应该如下修改
编译后运行图形化程序,测试失败,因为我再AccessController的构造器中将日志名设置为AccessControl了。如下,错误信息很明了。
以上介绍了NUnit测试框架和DotNetMock测试框架的作用并进行了演示,这两种测试框架十分适合.NET架构下的C#程序员编写单元测试代码,当然还有很多类似于NUnit的测试框架(如适用于Java语言的JUnit,其使用操作与NUnit几乎相同),类似于DotNetMock的测试框架(如NMock和POCMock,这些mock框架的API有细微的差异,但是底层代码都是基本一样的),正是由于这些优秀的测试架构的出现,才会使我们的代码质量得以显著提高。因此,我认为,单元测试是一个优秀而负责任的软件开发人员必须掌握的一门技术。