单元测试与Nunit的基本使用
一、单元测试是什么
单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,C#里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。
单元测试(模块测试)是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。
二、为什么需要单元测试
在我们现在的编程思维中一直都是编码=>编译=>调试,一直循环,直到要处理的功能完成,每一个功能完成都是如此,且有的功能是严重依赖于上一个功能。在如此处理中存在几个问题。
- 编译通过后,运行程序出现的bug难以定位。
- 修改一个bug,容易引进其他bug。
- Bug越到后期发现,越难以修改。
- 后期系统的复杂性,导致代码难以修改和重构,使得系统难以维护。
- 开发人员常认为编译功过,进行了几次手工测试就等于测试通过(认为详细的测试是测试人员的工作,非开发人员的工作)。
- 在完全依赖外部系统的情况下,难以进行有效的测试。
- 手工测试效率低下,针对性不强,测试不能重用。
有了单元测试在开发过程中起到的作用。
- 大大节约了测试和修改的时间,有效且便于测试各种情况。
- 能快速定位bug(每一个测试用例都是具有针对性)。
- 能使开发人员重新审视需求和功能的设计(难以单元测试的代码,就需要重新设计)。
- 强迫开发者以调用者而不是实现者的角度来设计代码,利于代码之间的解耦。
- 自动化的单元测试能保证回归测试的有效执行。
- 使代码可以放心修改和重构。
- 测试用例,可作为开发文档使用(测试即文档)。
- 测试用例永久保存,支持随时测试。
既然单元测试有这些好处,为什么我们不去用呢。可以归纳为以下几个理由。
- 对单元测试存在的误解,如:单元测试属于测试工作,应该由测试人员来完成,所以单元测试不属于开发人员的职责范围。答:虽然单元测试虽然叫做"测试",但实际属于开发范畴,应该由开发人员来做,而开发人员也能从中受益。
- 没有真正意识到单元测试的收益,认为写单元测试太费时,不值得。
答:在开发时越早发现bug,就能节省更多的时间,降低更多的风险。单元测试先期要编写测试用例,是需要多耗费些时间,但是后面的调试、自测,都可以通过单元测试处理,不用手工一遍又一遍处理。实际上总时间被减少了。
- 项目经理或技术主管没有要求写单元测试,所以不用写。
答:写单元测试应该成为开发人员的一种本能,开发本身就应该包含单元测试。
- 不知道有单元测试这回事,不知道如何用。经过这篇文档的说明,就基本知道如何处理单元测试。
结论:
只进行手工测试,只是临时性的单元测试,代码测试覆盖率要超过70%都很困难,未覆盖的代码可能遗留大量的细小的错误,这些错误还会互相影响,当bug暴露出来的时候难于调试,大幅度提高后期测试和维护成本。可以说,进行充分的单元测试,是提高软件质量,降低开发成本的必由之路。
要进行充分的单元测试,应专门编写测试代码,并与产品代码隔离。比较简单的办法是为产品工程建立对应的测试工程,为每个类建立对应的测试类,为每个函数(很简单的除外)建立测试函数。
单元测试是由程序员自己来完成,最终受益的也是程序员自己。可以这么说,程序员有责任编写功能代码,同时也就有责任为自己的代码编写单元测试。执行单元测试,就是为了证明这段代码的行为和我们期望的一致。
对于程序员来说,如果养成了对自己写的代码进行单元测试的习惯,不但可以写出高质量的代码,而且还能提高编程水平。
三、单元测试工具。
在.Net平台有三种单元测试工具,分别为MS Test、NUnit、Xunit.Net。
1.MS Test为微软产品,集成在Visual Studio 2008+工具中。
2.NUnit为.Net开源测试框架(采用C#开发),广泛用于.Net平台的单元测试和回归测试中,官方网址(www.nunit.org)。
3.XUnit.Net为NUnit的改进版。
(以下主要讲解NUnit的使用,会了NUnit其他2个测试工具也能快速熟悉)。
任何xUnit工具都使用断言进行条件的判断,NUnit自然也不例外,与其它的xUnit(如JUnit、phpUnit、pythonUnit)相比,由于大量使用了Generic、Attribute等语言特征,NUnit提供了更为方面、灵活的测试方法,下面先介绍一下断言。
NUnit一共有五个断言类,分别是Assert、StringAssert、FileAssert、DirectoryAssert、CollectionAssert,它们都在NUnit.Framework命名空间,其中Assert是常用的,而另外四个断言类,顾名思义,分别对应于字符串的断言、文件的断言、目录的断言、集合的断言。理论上,仅Assert类就可以完成所有条件的判断,然而,如果合理的运用后面的四个断言,将使代码更加简洁、美观,也更加便于理解和维护。
四、NUnit的使用。
本处演示所使用的NUnit版本为2.6.4,若要使用最新版可以去官网下载。
首先创建一个类库项目(也可以是其他项目),然后创建一个Test+类库名称的项目(也可以是项目名称+Test),用于代表是测试工程。如下图:
Demonstration项目中含有一个计算功能类,对应的测试项目含有一个测试计算类,一个计算功能类中方法可能需要多个测试用例来完成检测。如下展示出了2个类的代码:
- /// <summary>
- /// 用于演示的一个简单计算功能
- /// </summary>
- public class Calculate
- {
- /// <summary>
- /// 加法
- /// </summary>
- public int Add(int a, int b)
- {
- return a + b;
- }
- /// <summary>
- /// 减法
- /// </summary>
- public int Subtract(int a, int b)
- {
- return a - b;
- }
- /// <summary>
- /// 乘法
- /// </summary>
- public int Multiply(short a, short b)
- {
- return a * b;
- }
- /// <summary>
- /// 除法
- /// </summary>
- public int Quotient(int a, int b)
- {
- return a / b;
- }
- /// <summary>
- /// 开平方根
- /// </summary>
- public double SquareRoot(int num)
- {
- return Math.Sqrt(num);
- }
- /// <summary>
- /// 四舍五入,取整
- /// </summary>
- public int Round_Off(double num)
- {
- return (int)Math.Round(num);
- }
- /// <summary>
- /// 向上取整
- /// </summary>
- public int UpwardTrunc(double num)
- {
- return (int)Math.Ceiling(num);
- }
- /// <summary>
- /// 平方
- /// </summary>
- public int Square(short num)
- {
- throw new NotImplementedException();
- }
- }
- [TestFixture(Description = "测试示例")]
- public class TestCalculate
- {
- private Calculate calculate;
- private StreamReader reader;
- private string[] sourceData = new string[] { @"..\..\..\Resource\score_1.csv" };
- private short a, b;
- [TestFixtureSetUp]
- public void Initialize()
- {
- Console.WriteLine("初始化信息");
- calculate = new Calculate();
- }
- [TestFixtureTearDown]
- public void Dispose()
- {
- Console.WriteLine("释放资源");
- if (reader != null)
- {
- reader.Close();
- }
- }
- [SetUp]
- public void SetUp()
- {
- a = ;
- b = ;
- }
- [TearDown]
- public void TearDown()
- {
- Console.WriteLine("我是清理者");
- }
- [Test(Description = "加法")]
- [Category("优先级 1")]
- public void TestAdd()
- {
- Assert.AreEqual(, calculate.Add(a, b));
- }
- [Category("优先级 1")]
- [TestCase(, ), TestCase(, )]
- public void TestSubtract(int a, int b)
- {
- Assert.AreEqual(a - b, calculate.Subtract(a, b));
- }
- [Category("优先级 2")]
- [TestCase(, , Result = ), TestCase(, , Result = )]
- public int TestMultiply(short a, short b)
- {
- return calculate.Multiply(a, b);
- }
- [Test]
- [Category("优先级 2")]
- [ExpectedException(typeof(DivideByZeroException))]
- public void TestQuotient()
- {
- calculate.Quotient(a, );
- }
- [Test]
- [Category("优先级 3")]
- public void TestSquareRoot()
- {
- Assert.Less(, calculate.SquareRoot(a));
- }
- [Test]
- [Category("优先级 3")]
- [Sequential]
- public void TestRound_Off([Values(3.4, 4.5, 4.6, 5.5)] double num, [Values(, , , )] int result)
- {
- Assert.AreEqual(result, calculate.Round_Off(num));
- }
- [Test]
- [Category("优先级 3")]
- public void TestUpwardTrunc([ValueSource("sourceData")] object fileName)
- {
- reader = new StreamReader((string)fileName);
- string content;
- while ((content = reader.ReadLine()) != null)
- {
- var nums = content.Split(',').Select(c => double.Parse(c)).ToArray();
- Array.ForEach(nums, (num) =>
- {
- int result = calculate.UpwardTrunc(num);
- Console.Write(result + "\n");
- });
- }
- }
- [Test]
- public void TestSquare()
- {
- Assert.Throws<NotImplementedException>(() => calculate.Square(b));
- }
- [Test, Explicit]
- [Ignore]
- public void TestFactorial()
- {
- Assert.Fail("未能实现阶乘功能");
- }
- }
在粗略看了代码后,下面就详细说明相应的测试标记(属性)的用法。
- [TestFixture(arguments)]属性标记类为测试类,若没有填写参数,则测试类必须含有无参构造函数,否则需要相应的有参构造函数。也可以多个测试[TestFixture(1), TestFixture("a")]
- [Test]属性标记方法为测试方法,中添加Description参数可以给我们测试的功能添加描述信息。
- [TestCase(arguments)]属性标记有参数无返值方法为测试方法(泛型方法一样标记),想要多次测试可用逗号隔开([TestCase(1,2), TestCase(2,3)])。
- [TestCase(arguments,Result = value)属性标记带参数与返回值的方法为测试方法,执行的时候把预期的返回值也告诉NUnit,如果返回值不对,测试同样无法通过。
- [Suite](测试套件,仅对属性与索引器标记有效):可以将多个测试类组合到一起,同时执行多个测试。本版本的开发人员的一个信念就是减少这个的需要,可以使用[Category]来替代它。
- [Explicit]属性标记测试方法需要在UI界面显式执行,如果不想对某个方法进行单元测试,只是在它被选中时才进行测试的话,可以调用该特性。
- [Ignore]属性标记一个测试方法或一个测试类被忽略,如果测试类被忽略,其内中的测试方法也会被忽略。
- [ExpectedException(Type)]属性标记测试方法在运行时抛出一个期望的异常,如果是则测试通过,否则不通过。
- [Category("")]属性标记用于将测试分类(便于只测试需要的类别),可在方法与类上进行标记,在NUnit-GUI界面的Categories选项卡中对要测试种类进行添加,Run时仅测试该类别的测试。
- [TestFixtureSetUp]属性标记方法为类级别设置(初始化)方法,在整个测试类中执行一次初始化,所有的测试方法共享初始化数据。
- [TestFixtureTearDown]属性标记方法为类级别拆卸方法,在整个测试类中执行一次拆卸.当测试类中的所有测试方法执行完成,就会执行拆卸方法,用于清除数据、释放资源。
- [TearDown]属性标记方法为函数级别的拆卸方法,在执行完每个测试方法后,执行该拆卸方法。一个测试类可以仅有一个TearDown/Setup/TestFixtureSetUp/TestFixtureTearDown方法。如果有多个定义,测试类也会编译成功,但是测试时不会运行这些标记过的方法。
- [SetUp]属性标记方法为函数级别的设置方法,在执行每个测试方法前,执行该设置方法。
- 每执行一次Run,就是new一个新的实例在测试。
- [Maxtime]/[Timeout]属性标记测试用例的最大执行时间,前者超时时不取消测试,而后者会强行中断,用法如:[Test, Maxtime(2000)],[Test, Timeout(2000)]。
- [Repeat]属性标记测试方法重复执行多少次,如:[Test, Repeat(100)]。
- [RequiresMTA]/[RequiresSTA]/[RequiresThread]属性标记测试用例必须的在多线程、单线程、独立的线程状态下运行。
- [Values]属性标记测试用例的参数,以参数的形式传入一组值,NUnit会把这组值分解成相应数量的子测试。当测试用例的2个参数都使用[Values]进行标记,NUnit默认生成2组数量乘积的用例,需要使用[Sequential]标记测试用例才能按顺序生成一一对应的n(n=2组中最大数组长度)个子测试用例。
- [ValueSource]属性标记测试用例的参数,指定参数的数据源来自哪里,在使用[ValueSource]指定数据源时,该数据源必须实现了IEnumerable接口,数据源可以是属性、无参方法、实例或静态成员。
更多属性标记与详细说明,可以查阅NUnit官网提供的说明文档。一个方法的测试可能要写很多个测试用例,这都是正常的,如果一个测试用例包含多个断言,那些紧跟失败断言的断言都不会执行,因为通常每个测试方法最好只有一个断言。
在运行单元测试时有3种方式分别为:
- 把测试工程的属性=>调试=>启动外部程序,设置为NUnit运行程序。在启用调试时会启动NUnit界面程序,但NUnit界面没有测试用例的信息,需要自己添加在File=>Open Project->文件资源管理器,找你的测试工程类库或程序添加即可。点击Run运行,根据选中的节点运行该节点下所有的子测试用例(该测试可进行调试)。如下图:
以上的图片展示了运行错误界面和运行输出界面。在测试用例的节点中绿色'√'代表通过,黄色'√'代表忽略,红色'×'代表失败。
- 直接启动NUnit界面程序,在File=>Open Project->文件资源管理器,添加测试工程类库或程序,点击相应的节点进行Run测试,NUnit会根据类库或程序生成更新,自动更新界面中测试用例节点,但运行的测试用例不能进行调试。效果图与①中的效果一样。
- 在Visual Studio 2010+的IDE中以插件的方式集成NUnit测试工具,直接在测试工程中点击鼠标右键,运行测试即可。或者在VS菜单栏的测试中运行NUnit测试。集成与运行效果图在"第五节"中展示。
五、Nunit常用类和方法
1、Assert(断言):如果断言失败,方法将没有返回,并且报告一个错误。
1)、测试二个参数是否相等
Assert.AreEqual;
Assert.AreEqual;
2)、测试二个参数是否引用同一个对象
Assert.AreSame;
Assert.AreNotSame;
3)、测试一个对象是否被一个数组或列表所包含
Assert.Contains;
4)、测试一个对象是否大于另一个对象
Assert.Greater;
5)、测试一个对象是否小于另一个对象
Assert.Less;
6)、类型断言:
Assert.IsInstanceOfType;
Assert.IsAssignableFrom;
7)、条件测试:
Assert.IsTrue;
Assert.IsFalse;
Assert.IsNull;
Assert.IsNotNull;
Assert.IsNaN;用来判断指定的值是否为数字。
Assert.IsEmpty;
Assert.IsNotEmpty;
Assert.IsEmpty;
Assert.IsNotEmpty;
8)、其他断言:
Assert.Fail;方法为你提供了创建一个失败测试的能力,这个失败是基于其他方法没有封装的测试。对于开发你自己的特定项目的断言,它也很有用。
Assert.Pass;强行让测试通过
2、字符串断言(StringAssert):提供了许多检验字符串值的有用的方法
StringAssert.Contains;
StringAssert.StartsWith;
StringAssert.EndsWith;
StringAssert.AreEqualIgnoringCase;
3、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;集合的各项已经排序
4、FileAssert
FileAssert.AreEqual;
FileAssert.AreNotEqual;
5、DirectoryAssert
DirectoryAssert.AreEqual;
DirectoryAssert.AreNotEqual;
DirectoryAssert.IsEmpty;
DirectoryAssert.IsNotEmpty;
DirectoryAssert.IsWithin;
DirectoryAssert.IsNotWithin;
六、NUnit集成到VS中的使用。
在使用NUnit-GUI处理运行测试用例,是不是感觉比较麻烦,还要使用外部的NUnit应用程序,有没有简单点的最好能够跟VS开发工具紧密结合的方式来进行NUnit单元测试呢?答案是肯定的,有2种方式。
1.我们在VS中选择工具菜单栏下的扩展和更新,选择联机并在搜索框中输入NUnit。出现如下图的信息,有2个版本的Nunit适配器,分别为NUnit 3.x(最新版为3.4.1)和NUnit 2.x(最新版为2.6.4),都支持Visual Studio 2012+。若想在VS2010中集成,需要安装NUnit 2.6.4安装包(可在官网下载)与VS2010 NUnit整合插件(下载地址:
http://visualstudiogallery.msdn.microsoft.com/c8164c71-0836-4471-80ce-633383031099),下载安装完毕就能在 VS2010 的视图=>其他窗口中看到 Visual Nunit(或使用快捷键Ctrl + F7),打开该视图,将之拖到合适的位置。
下载安装NUnit Test Adapter后关闭VS,重启一下就好了,我们打开类库项目中的TestCalculate类,在右键弹出的菜单中点击运行测试。运行结束后,会在左侧的测试资源管理器当中显示本次操作的结果。
2.通过ReSharper工具处理NUnit的单元测试,在VS2010+中安装了ReSharper开发插件,ReSharper内中自带支持NUnit与MS Test这2个单元测试工具,只要你的测试工程中引用了相应的单元测试类库(如nunit.Framework.dll)、以及含有测试用例。通过鼠标右键或快捷键(Ctrl + T,R),就可以运行单元测试,也可以进行单元测试调试,ReSharper选项图与运行效果如下图。
七、后续
上面列出只能单元测试的基本使用,未能说明对Mock等其他功能的使用,也没有解释对难以单元测试的代码进行重新设计的说明,需要后期深入了解才能列出相应的文档说明。能够更好的使用单元测试才能更好的使用TDD(测试驱动开发)来开展项目,TDD测试驱动开发是测试先行(此测试是单元测试)、是极限编程的一个重要特点,它以不断的测试推动代码的开发,既简化了代码,同时也保证了软件指令,另一方面说编写的测试用例将成为重要文档(可以作为SDK提供给开发者,测试即文档)。
-----------------以上内容是根据博客园其他博客的说明与Nunit官方文档,以及自己测试使用,进行了整理说明。----------------------------
单元测试与Nunit的基本使用的更多相关文章
- 单元测试(一)-NUnit基础
单元测试作为提高代码和软件质量的有效途径,其重要性和益处自不必多说,虽然我没有实践过TDD之类,但坚信单元测试的积极作用.作为一种开发方法,单元测试早在上世纪70年代就已经在Smalltalk语言被运 ...
- 单元测试工具NUnit的使用
使用 NUnit 工具来进行单元测试 首先在要创建一个单元测试的项目,通常在原有的解决方案中添加新项目, 在弹出的项目类型中选择单元测试,项目的命名一般情况下与解决方案的名称相同后加UnitTes ...
- C#单元测试:NUnit详细使用方法
1. TDD的简介 首先什么是TDD呢?Kent Beck在他的<<测试驱动开发 >>(Addison-Wesley Professional,2003)一书中,使用下面2个原 ...
- 如何快速上手.net下单元测试工具NUnit?
NUnit基本使用 准备知识: 读此博文需要了解单元测试基本概念及NUnit的的安装. 传送门:单元测试之道(使用NUnit) 1.常见的错误 当学习一个新东西时,先学习错误,是最快的方式. 1.1 ...
- [测试]单元测试框架NUnit
说到测试,相信大家都或多或少了解. 按照各自分类,就自己知道包括 A.单元测试.集成测试.系统测试 B.白盒测试.黑盒测试 C.压力测试.性能测试.安全测试 ...... 反正是太多太多.就做开发以来 ...
- 单元测试工具Nunit
原文链接:http://blog.csdn.net/snowshinoy/article/details/6951352 调试 附加到: nunit-agent.exe进程:
- Nunit工具做C#的单元测试
Nunit工具做C#的单元测试 学习心得 编写人:罗旭成 时间:2013年9月2日星期一 1.开发人员如何做单元测试 单元测试是针对最小的可测试软件元素(单元)的,它所测试的内容包括单元的内部结构 ...
- 实现VS2010整合NUnit进行单元测试(转载)
代码编写,单元测试必不可少,简单谈谈Nunit进行单元测试的使用方式: 1.下载安装NUnit(最新win版本为NUnit-2.6.4.msi) http://www.nunit.org/index. ...
- 5分钟实现VS2010整合NUnit进行单元测试
本文转载自:http://www.cnblogs.com/jeffwongishandsome/archive/2012/03/18/2404845.html 1.下载安装NUnit(最新win版本为 ...
随机推荐
- Javascript函数节流
最近在做网页的时候有个需求,就是浏览器窗口改变的时候需要改一些页面元素大小,于是乎很自然的想到了window的resize事件,于是乎我是这么写的 <!DOCTYPE html> < ...
- Linux用户切换
1,查看当前user: whoami 2,普通用户切换到root,会要求输入密码: su 3,root切换到普通用户: su - username
- 302 Moved Temporarily
这个就是表示 重定向!! 不过,302在不同HTTP协议下的状态信息不同. Moved temporarily (redirect) 你所连接的页面进行了Redirect Found 类似于301,但 ...
- flex 阅读器
遇到很多的困难 首先是 pdf2swf 而后又下载swftools 而后有swfcombine.exe 让制作的swf 文字可选? —— 这应该是很常见的需求啊! 可是我搜索来搜索去都找不到... 搜 ...
- 说说设计模式~适配器模式(Adapter)
返回目录 之前和大家一起谈了工厂模式和单例模式,今天来看一下另一种非常常用的模式,它就是适配器模式,第一次看到这个模式是通过“张逸”老师的“设计之道”这篇文章,在这里表adapter讲的很透彻,今天把 ...
- MVVM架构~knockoutjs系列之为validation.js扩展minLength和maxLength
返回目录 为什么要对minLength和maxLength这两个方法进行扩展呢,是因为这样一个需求,在用户注册时,可以由用户自己决定他们输入的字符,中文,英文,数字均可,这样做了之后,使用户的体验更好 ...
- 在jQuery ajax中按钮button和submit的区别分析
在使用jQuery ajax的get方法进行页面传值,不能用submit,否则无刷新获取数据展示 点击submit提交按钮,sendPwd.php通过$_POST接收传过来的值,然后echo一段数据. ...
- -bash: /usr/local/bin/react-native: No such file or directory
执行react-native run-android/run-ios的时候出现 -bash: /usr/local/bin/react-native: No such file or director ...
- php的mysql\mysqli\PDO(一)mysql
原文链接:http://www.orlion.ga/1140/ 工作中数据库的操作都被封装好了,这些怎么用的都快忘了干脆写篇博客重新复习下,以后要是再忘记了可以看这篇文章. PHP 5.5.0 起已废 ...
- Android Handler的使用示例:结合源码理解Android Handler机制(一)
什么是Handler? Android 的官方解释: 文档分节1:A Handler allows you to send and process Message and Runnable objec ...