单元测试是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。例如,你可能把一个很大的值放入一个有序list 中去,然后确认该值出现在list 的尾部。或者,你可能会从字符串中删除匹配某种模式的字符,然后确认字符串确实不再包含这些字符了。
执行单元测试,是为了证明某段代码的行为确实和开发者所期望的一致。
当编写项目的时刻,如果我们假设底层的代码是正确无误的,那么先是高层代码中使用了底层代码;然后这些高层代码又被更高层的代码所使用,如此往复。当基本的底层代码不再可靠时,那么必需的改动就无法只局限在底层。虽然你可以修正底层的问题,但是这些对底层代码的修改必然会影响到高层代码。于是,一个对底层代码的修正,可能会导致对几乎所有代码的一连串改动,从而使修改越来越多,也越来越复杂。从而使整个项目也以失败告终。
而单元测试的核心内涵:这个简单有效的技术就是为了令代码变得更加完美。
NUnit介绍
NUnit 是一个免费开源的(http://www.nunit.org)产品,它提供了一套测试框架和一个测试运行程序(test runner)。
注意:test tunner 知道如何寻找具有 [TestFixture] 属性的类和类中的 [Test] 方法。
如何安装 NUnit
在官网http://www.nunit.org/index.php?p=download下载NUnit,当前最新的版是2.4.8,我下的是NUnit-2.4.8-net-2.0.zip。
NUnit第一个演示
我们用Visual Studio 2008新建一个NUnit项目
为了便于演示,我们把默认的Program.cs改成Calculator.cs,在Calculator类里,我们实现简单的加减乘除四个方法。完整代码如下:
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/None.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/None.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/None.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedBlockStart.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedSubBlockStart.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedSubBlockStart.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedSubBlockEnd.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedSubBlockStart.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedSubBlockEnd.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedSubBlockStart.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedSubBlockEnd.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedSubBlockStart.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedSubBlockEnd.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedSubBlockStart.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedSubBlockEnd.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedSubBlockStart.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedSubBlockEnd.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedSubBlockStart.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedSubBlockEnd.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedSubBlockStart.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedSubBlockEnd.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedSubBlockStart.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedSubBlockEnd.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedSubBlockEnd.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedBlockEnd.gif)
如果没有单元测试,我们普通的测试方法就像是Main方法一样,这样的测试是一个很邪恶的测试方法,花时间且很难得到我们想要的结果。
那么,我们应该如何来用NUnit做单元测试呢?
我们再新建一个项目
为这个NUnitTestTest引用“NUnitTest项目”和“nunit.framewor类库”。我们再新建一个测试类,命名为“CalculatorTest.cs”。并键入如下代码
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/None.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/None.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/None.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/None.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/None.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedBlockStart.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedSubBlockStart.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedSubBlockStart.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedSubBlockEnd.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedSubBlockEnd.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedBlockEnd.gif)
这就是一个简单的单元测试方法了。首先我们使用using NUnit.Framework和using NUnitTest,因为接下来的代码需要用到这两个命名空间。在这里,我们要注意几点,NUnit测试用的类前面一定要加上[TestFixture],以表示这是NUnit测试类;测试方法一定是public的,且没有返回值。这里的TestFixture和Test都是NUnit的Attribute,下表给出了NUnit常用的Attribute
usage |
NUnit attributes |
标识测试类 |
TestFixture |
标识测试用例(TestCase) |
Test |
标识测试类初始化函数 |
TestFixtureSetup |
标识测试类资源释放函数 |
TestFixtureTearDown |
标识测试用例初始化函数 |
Setup |
标识测试用例资源释放函数 |
TearDown |
标识测试用例说明 |
N/A |
标识忽略该测试用例 |
Ignore |
标识该用例所期望抛出的异常 |
ExpectedException |
标识测试用例是否需要显式执行 |
Explicit |
标识测试用例的分类 |
Category |
Assert.AreEqual是断言,在测试框架中,断言是单元测试的核心,我们在测试中要对其程序断言。如果某个断言失败,方法的调用不会返回值,并且会报告一个错误。如果一个测试包含多个断言,那些紧跟失败断言的那些断言都不会执行,因此每个测试方法最好只有一个断言。NUnit.Framework.Assert有23个重载方法,大部分的情况它都有考虑到,当然,不排除需要自己写一个复杂的断言方法。
上面的代码中,int expected = 5;是指我们期望程序执行的结果是5,int actual = cal.Add(2, 3);则执行Calculator.Add方法得到实际的值。
顺便说一下,CalculatorTest(类名)还有TestAdd(方法名)并不是一定要这样写,你可以自由的命名你的名称,不过为了让你的代码可读性更好,请遵循一个命名规范,这个规范可以是公司定的也可以是网上主流的命名规则。
对Add()方法的单元测试代码已经完成了,接下来我们运行下载解压后文件夹中的nunit.exe,程序界面如图
打开对话"File"/"Open Project..."对话框,或者按"Ctrl + O",把第二个单元测试项目NUnitTestTest生成的NUnitTestTest.dll加载进来
我们点右边的"Run"按钮执行单元测试
太棒了,绿色!通过!Keep the bar green to keep the code clean.
一个简单的单元测试过程就是这样的。
我们再为除法写一个单元测试方法
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/None.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/None.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedBlockStart.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedBlockEnd.gif)
重新生成NUnitTestTest项目,NUnit会自动把TestDivide方法加进去。
再点"Run",通过测试。大家都知道除法中除数不能为0,如果这里除数是0呢?会有什么样的结果?
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/None.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/None.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedBlockStart.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/InBlock.gif)
![](http://www.moozi.net/editors/fckeditor/editor/plugins/mzplugins/images/ExpandedBlockEnd.gif)
生成项目并重新运行单元测试,
测试没有通过“NUnitTestTest.CalculatorTest.TestDivide:System.DivideByZeroException : 试图除以零。”这时,我们要返回到Calculator类中修改Divide方法使之除数为0时返回其它的值。
NUnit第一个简单示例就先到这里,在NUnit的官网也有简单教程,大家可以看看。http://www.nunit.org/index.php?p=quickStart&r=2.4.8
在单元测试中,我们在做正面的测试的同时也要做一些反面测试,这样才能让我们的代码更健壮。