在.NET开发中的单元测试工具之(1)——NUnit
NUnit介绍
NUnit是一个专门针对于.NET来写的单元测试框架,它是xUnit体系中的一员,在xUnit体系中还有针对Java的JUnit和针对C++的CPPUnit,在开始的时候NUnit和xUnit体系中的大多数的做法一样,仅仅是将Smalltalk或者Java版本转换而来,但是在.NET2.0之后它加入了一些特有的做法。NUnit的官方网站是:http://www.nunit.org/,目前的最新版本是:2.6.2。
NUnit下载与安装
NUnit的每个版本都提供了两种形式的下载:安装文件和免安装方式,分别是*.msi格式和*.zip格式。前者需要安装才能使用,并且会在安装过程中创建一些快捷方式和注册NUnit的dll到GAC,这样以后编写NUnit测试类的时候添加NUnit的dll就像添加.Net Framework的dll一样。如果是下载的zip格式的文件,则不会创建快捷方式和注册dll,在编写单元测试类时需要手动指定NUnit的dll的路径。
NUnit的运行有三种方式:命令行和图形用户界面。以周公当前电脑上安装的NUnit2.5.10为例,安装路径为:C:\Program Files (x86)\NUnit 2.5.10,其下有三个目录:bin、doc和samples。在doc目录下是软件的文档(英文),在samples目录下则是一些样例代码。如果是采用免安装模式的话,运行NUnit就需要运行bin目录下的文件,在bin目录下有net-1.1和net-2.0两个文件夹,分别对应.net的不同版本。
下面介绍如何以不同的方式启动NUnit:
命令行模式:运行nunit-console.exe。
图形用户界面模式:运行nunit.exe。
并行(parallel)模式:运行pnunit-launcher.exe。
注意:.Net2.0版本的NUnit是使用/platform:anycpu参数来编译的,我们知道这样的结果是运行在x86的系统上会被JIT编译成32位的程序,而在x64的系统上会被JIT编译成64位的程序。如果使用NUnit在x64系统上测试32位的程序就会带来问题。为了避免这个问题,可以使用nunit-agent-x86.exe/nunit-x86.exe来测试,因为在编译的时候使用了/platform:x86作为编译参数。
下图是运行NUnit的GUI界面:
NUnit的常用Attribute标记
这些都是可以用来作为类或者方法的属性,它们都是System.Attribute类的直接或间接子类,有如下:
Category:用来将测试分类。这个在主界面可以看到Tests/Categories两个选项卡,如果给方法标记了Category属性就会在Categories选项卡中看得到。
Combinatorial:用来将来测试时需要测试各种可能的组合,比如如下代码:
- [Test, Combinatorial]
- public void MyTest(
- [Values(1, 2, 3)] int x,
- [Values("A", "B")] string s)
- {
- string value = x + s;
- Assert.Greater(2, value.Length);
- }
测试时实际会测试6种情况:MyTest(1, "A")/MyTest(1, "B")/MyTest(2, "A")/MyTest(2, "B")/MyTest(3, "A")/MyTest(3, "B")。
Culture:设置测试时的语言环境,这对我们测试一些语言敏感的场景下有用,比如DateTime.ToString()在不同语言环境下得到的字符串并不相同。
Description:用于指定测试对应的描述,如果选择将测试结果生成XML文件,那么就会在XML文件中看到这些描述。
ExpectedException:指出执行测试时将会抛出Exception。
Explicit:如果测试的类或者方法使用此Attribute,那么在使用带GUI的NUnit测试时这个类或者方法必须在界面上选定才会被执行。
Explicit:忽略某个测试类或者方法。
Maxtime:测试方法最大执行的毫秒数,如果程序的执行时间超过指定数值,那么就会被认为测试失败。
Random:用于指定如何随机生成参数来测试方法。如下面的代码:
- [Test]
- public void TestDemo1(
- [Values(1, 2, 3)] int x,
- [Random(-10,10,2)] int y)
- {
- Assert.Greater(x + y, 0);
- }
表示方法TestDemo1会生成6个测试,1,2,3分别作为参数x的值与两次从-10到10之间的随机数y组成6次测试。
Range:指定参数的方法,如下面的方法:
[Test]
public void TestDemo2(
[Range(0, 11, 4)] int x)
{
Assert.AreEqual(x%3,0);
}
表示从0开始递增,步长为4,且不大于11。
Repeat:将重复测试的次数。
RequiresMTA:表示测试时需要多线程单元(multi-threaded apartment)。
RequiresSTA:表示测试时需要单线程单元(single-threaded apartment)。
SetUp:在每个测试方法开始之前执行的初始化操作。在NUnit 2.5之前要求每个类只能有一个带SetUp属性的实例方法,但在NUnit 2.5之后则没有次数和必须是实例方法的限制。
TearDown:与SetUp的作用相反,是在每个测试方法执行结束之后执行的方法。在NUnit 2.5之前要求每个类只能有一个带SetUp属性的实例方法,但在NUnit 2.5之后则没有次数和必须是实例方法的限制。
Test:用来标记需要测试的方法,在NUnit 2.5之前只能用于标记实例方法,在NUnit 2.5之后则可以用于标记静态方法。
TestCase:标记方法具有参数并且提供了在测试时需要的参数。如下面的代码:
[TestCase(12, 3, 4)]
[TestCase(12, 2, 6)]
[TestCase(12, 4, 3)]
public void DivideTest(int n, int d, int q)
{
Assert.AreEqual(q, n / d);
}
将会执行三次测试,相当于:
[Test]
public void DivideTest()
{
Assert.AreEqual(4,12/3);
}
[Test]
public void DivideTest()
{
Assert.AreEqual(6,12/2);
}
[Test]
public void DivideTest()
{
Assert.AreEqual(3,12/4);
}
TestFixture:标记一个类可能具有[Test]/[SetUp]/[TearDown]方法,但这个类不能是抽象类。
TestFixtureSetUp:标记在类中所有测试方法执行之前执行的方法。在NUnit 2.5之前只能在类中将此标记最多使用于一个实例方法,在NUnit 2.5之后则可以标记多个方法,而且不限于实例方法还可以用于静态方法。
TestFixtureTearDown:标记在类中所有测试方法执行之后再执行的方法。在NUnit 2.5之前只能在类中将此标记最多使用于一个实例方法,在NUnit 2.5之后则可以标记多个方法,而且不限于实例方法还可以用于静态方法。
Timeout:标记被测试的方法最大的执行时间,如果超出标记的时间,则会被取消执行并且被标记为测试失败。
Values:标记作为测试方法的一系列的参数。前面的代码实例中就有用法实例。
NUnit的断言(Assertions)
断言是所有基于xUnit单元测试系列的核心,NUnit通过NUnit.Framework.Assert类提供了丰富的断言。具体说来,NUnit总共提供了11个类别的断言,它们是:
Equality Asserts:用于断言对象是否相等方面的断言,主要表现为两个方法的重载:Assert.AreEqual()和Assert.AreNotEqual()两种形式的重载,重载参数包括了常见的基本数值类型(int/float/double等)和引用类型(表现为使用object作为参数).
Identity Asserts:用于判断引用类型的对象是否是同一个引用的断言及断言对象是否存在于某个集合中,如Assert.AreSame、Assert.AreNotSame及Assert.Contains。
Condition Asserts:用于某些条件的断言,如:Assert.IsTrue、Assert.True、Assert.IsFalse、Assert.False、Assert.IsNull、Assert.Null、Assert.IsNotNull、Assert.NotNull、Assert.IsNaN、Assert.IsEmpty及Assert.IsNotEmpty。
Comparisons Asserts:用于数值及实现了IComparable接口的类型之间的断言,如Assert.Greater(大于)、Assert.GreaterOrEqual(大于或等于)、Assert.Less(小于)、Assert.LessOrEqual(小于或等于)。
Type Asserts:用于类型之间的判断,比如判断某个实例是否是某一类型或者是从某个类型继承,如:Assert.IsInstanceOfType、Assert.IsNotInstanceOfType、Assert.IsAssignableFrom、Assert.IsNotAssignableFrom。在NUnit 2.5之后就增加了泛型方法,如Assert.IsInstanceOf<T>、Assert.IsNotInstanceOf<T>、Assert.IsAssignableFrom<T>、Assert.IsNotAssignableFrom<T>。。
Exception Asserts:有关异常方面的断言,如Assert.Throws/Assert.Throws<T>、Assert.DoesNotThrow、Assert.Catch/Assert.Catch<T>。
Utility Methods:用于精确控制测试过程,总共有四个方法,分别是:Assert.Pass、Assert.Fail、Assert.Ignore、Assert.Inconclusive。Assert.Pass和Assert.Fail是相反的,前者是表示将立即终止测试并将测试结果标识为成功通过测试,后者是立即终止测试并将测试结果标识为测试失败。Assert.Ignore表示忽略测试,这个标记可以用于标识测试方法或者测试的类。
StringAssert:用于字符串方面的断言,提供的方法有StringAssert.Contains、StringAssert.StartsWith、StringAssert.EndsWith、StringAssert.AreEqualIgnoringCase及StringAssert.IsMatch。
CollectionAssert:关于集合方面的断言,提供的方法有CollectionAssert.AllItemsAreInstancesOfType、CollectionAssert.AllItemsAreNotNull、CollectionAssert.AllItemsAreUnique、CollectionAssert.AreEqual、CollectionAssert.AreEquivalent、CollectionAssert.AreNotEqual、CollectionAssert.AreNotEquivalent、CollectionAssert.Contains、CollectionAssert.DoesNotContain、CollectionAssert.IsSubsetOf、CollectionAssert.IsNotSubsetOf、CollectionAssert.IsEmpty、CollectionAssert.IsNotEmpty和CollectionAssert.IsOrdered。
FileAssert:用于文件相关的断言,主要提供两个方法:FileAssert.AreEqual和FileAssert.AreNotEqual。
DirectoryAssert:用于文件夹的断言,提供的方法有:DirectoryAssert.AreEqual、DirectoryAssert.AreNotEqual、DirectoryAssert.IsEmpty、DirectoryAssert.IsNotEmpty、DirectoryAssert.IsWithin和DirectoryAssert.IsNotWithin。
NUnit的使用
第一次打开NUnit时会是一个空白界面,如下图所示:
首先我们需要创建一个NUnit项目,点击[File]->[New Project]会弹出一个保存NUnit项目的对话框,选择合适的路径并输入合适的名称(注意文件后缀名为.nunit),然后点击保存按钮,这样就创建了一个NUnit测试项目。以后我们就可以再次打开这个项目了。
此时这个NUnit项目中还不包含任何单元测试用例,我们需要创建包含测试用例的项目。打开Visual Studio创建一个类库项目(在真实项目中通常做法是向当前解决方案中添加类库项目,这样便于解决dll引用问题),接着我们需要添加NUnit的引用,这取决于我们是采用安装方式还是免安装方式,通常情况下我们只需要添加对nunit.framework(对应的dll是unit.framework.dll)的引用就够了。
这里周公采用的示例代码如下:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using NUnit.Framework;
- namespace UnitTestDemo
- {
- [TestFixture]
- public class NUnitTestDemo
- {
- private IList<int> intList = new List<int>();
- [SetUp]
- [Category("NA")]
- public void BeforeTest()
- { Console.WriteLine("BeforeTest"); }
- [TestFixtureSetUp]
- [Category("NA")]
- public void BeforeAllTests()
- { Console.WriteLine("BeforeAllTests"); }
- [TearDown]
- [Category("NA")]
- public void AfterTest()
- { Console.WriteLine("AfterTest"); }
- [TestFixtureTearDown]
- [Category("NA")]
- public void AfterAllTests()
- { Console.WriteLine("AfterAllTests"); }
- [Test]
- [Category("NA")]
- public void Test1()
- { Console.WriteLine("Test1"); }
- [Test]
- [Category("NA")]
- public void Test2()
- { Console.WriteLine("Test2"); }
- [Test]
- public void TestFloat()
- {
- float value = 0.9999999999999999999999999999f;
- //value = 0.9999999999999999999999999999;
- Console.WriteLine("float value:" + value);
- Assert.AreEqual(value, 1f);
- Console.WriteLine("TestFloat");
- }
- [Test]
- public void TestDouble()
- {
- double value = 0.9999999999999999999999999999d;
- Console.WriteLine("double value:" + value);
- Assert.AreEqual(value, 1d);
- Console.WriteLine("Test2");
- }
- [Test]
- public void TestDecimal()
- {
- decimal value = 0.9999999999999999999999999999M;
- Console.WriteLine("decimal value:" + value);
- Assert.AreEqual(value, 1M);
- Console.WriteLine("Test2");
- }
- [Test,Repeat(3)]
- public void TestIntList2()
- {
- Assert.AreEqual(0, intList.Count);
- }
- [Test]
- public void TestIntList1()
- {
- intList.Add(1);
- Assert.AreEqual(1, intList.Count);
- }
- [TestCase(12, 3, 4)]
- [TestCase(12, 2, 6)]
- [TestCase(12, 4, 3)]
- public void DivideTest(int n, int d, int q)
- {
- Assert.AreEqual(q, n / d);
- }
- [Test, Combinatorial,Description("This is used for show Combinatorial")]
- public void MyTest(
- [Values(1, 2, 3)] int x,
- [Values("A", "B")] string s)
- {
- string value = x + s;
- Assert.Greater(2, value.Length);
- }
- [Test]
- public void TestDemo1(
- [Values(1, 2, 3)] int x,
- [Random(-10,10,2)] int y)
- {
- Assert.Greater(x + y, 0);
- }
- [Test]
- public void TestDemo2(
- [Range(0, 11, 4)] int x)
- {
- Assert.AreEqual(x%3,0);
- }
- }
- }
编译项目生成dll。我们就可以在NUnit主界面上点击[Project]->[Add Assembly...]来添加刚才编译生成的dll,加载成功后界面如下所示:
点击界面上的[Run]按钮就可以开始测试了。注意这种方式下是测试所有的测试方法,如果我们只想测试某几个方法,可以勾选方面前面的复选框(默认情况下复选框不出现,需要按照点击[Tools]->[Setting]打开设置界面,然后点击在[GUI]下面找到[Tree Display],勾选上“Show CheckBoxes”即可)。
如果我们只是想单独测试某个方法,那就更简单了——直接双击那个测试方法即可。
有时候我们进行测试时还会用到一些config文件里面的配置信息,如在app.config/web.config中保存数据库连接字符串信息及其他的配置信息,为了能让NUnit测试时能读取app.config/web.config中保存的配置信息,我们需要对NUnit进行配置。
为了演示,我们制定以下信息:
项目名称:UnitTestDemo
项目位置:D:\BlogCode\UnitTestDemo\
项目编译模式(Debug/Release):Debug
为了演示刚才的如何对config文件中保存的数据进行测试,我们在刚才的代码基础上编写了三个测试用例,代码如下:
- [Test]
- public void Test0_51CTOBlog()
- {
- StringAssert.AreEqualIgnoringCase(ConfigurationManager.AppSettings["51ctoBlog"], "http://zhoufoxcn.blog.51cto.com");
- }
- [Test]
- public void Test0_CSDNBlog()
- {
- StringAssert.AreEqualIgnoringCase(ConfigurationManager.AppSettings["CSDNBlog"], "http://blog.csdn.net/zhoufoxcn");
- }
- [Test]
- public void Test0_SinaWeiBo()
- {
- StringAssert.AreEqualIgnoringCase(ConfigurationManager.AppSettings["SinaWeiBo"], "http://weibo.com/zhoufoxcn");
- }
同时在app.config文件的appSettings节点增加以下数据:
- <appSettings>
- <add key="51ctoBlog" value="http://zhoufoxcn.blog.51cto.com"/>
- <add key="CSDNBlog" value="http://blog.csdn.net/zhoufoxcn"/>
- <add key="SinaWeiBo" value="http://weibo.com/zhoufoxcn"/>
- </appSettings>
如果不在NUnit上做任何设置,我们会得到错误的结果,如下图所示:
这时,我们可以按照如下步骤配置,点击[Project]-[Edit...]打开如下界面:
在上图的界面中设置ApplicationBase为当前要测试的dll所在的路径,本例中为:D:\BlogCode\UnitTestDemo\bin\Debug(注意如果复制全路径到文本框中NUnit会自动更改为相对路径),因为当前项目是名为UnitTestDemo的类库项目,所以对应config文件名称为UnitTestDemo.dll.config,将其填入Configuration File Name后面的文本框中,然后我们再次点击[Run]按钮就会看到测试通过。
总结
作为xUnit体系中的一员,NUnit确实给.Net开发人员进行单元测试带来了不少方便,在早期我们一直都是使用NUnit进行单元测试的。但是也存在着一些不足之处,比如:1.在xUnit体系中的JUnit是在测试每个方法时都是新生成一个实例,而在NUnit中确实一个TestFixture只会生成一个实例,这样一来如果对要包含单元测试类中的实例数据进行更改会可能会影响到其它的测试方法(像JUnit那样每次都生成一个实例则不会产生这种情况)。2.早期大多数人以为像JUnit中一样,[SetUp]、[TearDown]只会在所有测试前、后分别执行一次,实际情况是在每个测试前、后都会执行一次,为了达到JUnit中[SetUp]、[TearDown]这样的效果,只能新增TestFixtureSetUp、TestFixtureTearDown属性。除此之外,还存在一些缺点和不足。
所以本篇只是简单介绍了NUnit的一些用法,虽然NUnit提供了相当多的断言及Attribute,但实际用到的并不多,在这里介绍它是为介绍另一个.NET单元测试工具作铺垫。
周金桥
2013-01-03
在.NET开发中的单元测试工具之(1)——NUnit的更多相关文章
- 在.NET开发中的单元测试工具之(2)——xUnit.Net
在上一篇<在.NET开发中的单元测试工具之(1)——NUnit>中讲述了如何使用NUnit在.NET开发中进行单元测试以及NUnit的一些缺点,今天将讲述如何使用xUnit.Net来进行单 ...
- 在.NET开发中的单元测试工具之——NUnit
NUnit介绍 NUnit是一个专门针对于.NET来写的单元测试框架,它是xUnit体系中的一员,在xUnit体系中还有针对Java的JUnit和针对C++的CPPUnit,在开始的时候NUnit和x ...
- iOS开发中的单元测试(三)——URLManager中的测试用例解析
本文转载至 http://www.cocoachina.com/cms/plus/view.php?aid=8088 此前,我们在<iOS开发中的单元测试(一)&(二)>中介绍 ...
- 【Unity游戏开发】浅谈Unity游戏开发中的单元测试
一.单元测试的定义与作用 单元测试定义:单元测试在传统软件开发中是非常重要的工具,它是指对软件中的最小可测试单元进行检查和验证,一般情况下就是对代码中的一个函数去进行验证,检查它的正确性.一个单元测试 ...
- Android开发中的logcat工具使用
http://os.51cto.com/art/200905/126051.htm 用adb直接查看log: adb logcat 清除之前的log: adb logcat -c 加过滤查看lo ...
- Java开发中模拟接口工具moco的使用
场景 在开发中需要依赖一些接口,比如需要请求一个返回Json数据的接口,但是返回Json数据的接口要么是没搭建,要么是交互比较复杂. 此时,就可以使用moco来模拟接口返回接口数据,以便开发和测试工作 ...
- Cocos开发中性能优化工具介绍之Visual Studio内存泄漏检测工具——Visual Leak Detector
那么在Windows下有什么好的内存泄漏检测工具呢?微软提供Visual Studio开发工具本身没有什么太好的内存泄漏检测功能,我们可以使用第三方工具Visual Leak Detector(以下简 ...
- java开发中的一些工具软件
1. XJad, 反编译工具,类似于.Net中的Refractor.可以反编译单个jar文件或一个文件夹下的class文件,效果还不错. 2. dirtyJOE, class文件直接修改工具.有时想修 ...
- Cocos开发中性能优化工具介绍之使用Windows任务管理器
说到Windows平台,我们很快就想到了Visual Studio 2012,然而Visual Studio 2012在这方面没有很好的工具.如果我们只是想知道大体上内存.CPU等在某一事件前后变化情 ...
随机推荐
- P2837 晚餐队列安排
此题可能用动规不太好做,主要是状态转移方程很难想个人认为,思维发散的大佬们忽视. 我看了这位大佬的dp题解后才想到了方程,在此受我一膜%%% 嗯,说下思路: 先用a[i]数组存一下输入的编号: 然后用 ...
- 【XSY2925】cti 网络流
题目描述 有一个 \(n\times m\)的网格,每个格子里面可能有一些炮塔,或者有几个人. 每个炮塔可以在给定的方向(上下左右)上选一个点作为它的攻击位置,然后消灭这个格子里面的所有人.当然也可以 ...
- 洛谷P1776 宝物筛选
一道很好的单调队列优化多重背包入门题 令\(v[i]\)表示重量,\(w[i]\)表示价格 ,\(c[i]\)表示最多可放的数量,不难推出朴素的转移方程如下: \(f[i][j]=max\{f[i-1 ...
- 线性布局LinearLayout
常用属性 id:控件唯一属性 android:id="@+id/ll_1" --------------------------------------- layout_width ...
- 寒冬之下,移动开发没人要了? 浅谈 iOS 开发者该 何去何从?
前言: 作者 | 梅梅 文章来源 CSDN 对于移动互联网而言,2018 年像是球场上的一声裁判哨.哨声响起,高潮迭起的上半场结束.本该再创辉煌的下半场,还没开赛却被告之:规则改变.场地收缩.教 ...
- usb输入子系统键盘(四)
目录 usb输入子系统键盘 设计思路 内核的上报代码 完整代码 title: usb输入子系统键盘 tags: linux date: 2018/12/20/ 17:05:08 toc: true - ...
- Go语言系列(八)- Goroute和Channel
一.Goroute 1. 进程和线程 A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配合调度的一个独立单位 B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能 ...
- 【.net】ASP.Net设置和取消设置web项目起始页
#在visual studio中设置和取消web项目的起始页 方法一:在所要设置的页面上右键->设为起始页 方法二:web项目上右键->属性页 website项目: tips:如果取消要取 ...
- openstack项目【day24】:VLAN模式
本节内容 一 二层基础知识 1.1 vlan介绍 1.1.1:vlan的含义 1.1.2:vlan的类型 1.1.3:vlan的不足 1.2 : 二层交换的基础知识 1.2.1:二层交换机最基本的功能 ...
- 解决 IIS 反向代理ARR URLREWRITE 设置后,不能跨域跳转 return Redirect 问题
1.选择iis根节点,点击ARR 2.选择代理设置 3.去掉reverse rewrite host in response headers的勾选,点击应用即可