Nunit单元测试
计算机专业 薛会萍 S10111295
摘要:随着软件规模的不断扩大和复杂性提高,测试环节越来越受到重视。本文首先描述软件测试的基本知识,着重叙述单元测试,然后介绍一些单元测试的工具,最后再使用NUnit进行一些实践测试,并进行小结。
关键字:软件测试 单元测试 NUnit
正文:
1. 软件测试
1983年,IEEE定义软件工程为“软件工程是开发、运行、维护和修复软件的系统方法”。这个定义强调了软件工程是一种系统方法,而不是个人技巧。在软件的生命周期中使用一套技术,来完成各个阶段的详细任务,这套技术的集合成为软件工程方法学。目前使用最广泛的软件工程方法学,分别是传统方法学和面向对象方法学,他们都包含三个要素:方法、工具和过程[1]。
犹如课程学习的时候要进行单元测试、综合测试,或者比较正式的期中考试和期末考试,软件开发的过程也是如此。软件的测试贯穿于软件的整个生命周期,从需求阶段的确认需求分析,再到设计和编码阶段的功能完整、代码不出错,以及最后的集成部署,都离不开软件测试。
图1 软件测试与各个开发阶段的关系
如图1所示,软件工程的过程包括了需求分析、概要设计、详细设计、编码、单元测试、集成测试、确认测试等过程,当然这只是常见的一种,目前的分法有很多种,但是大致的也是这个样子。单元测试对应的是编码和详细设计阶段,也就是说,单元测试检验的对象是编码和详细设计阶段的工作成果,在此步骤完成后,再进行集成测试和确认测试。集成测试和确认测试的步骤也单元测试类似,都有自己的对象,这里不再赘述。
软件测试的步骤也可以如图2所示,很多个单元测试完成之后,当然是各个模块了,才能进入集成测试。这点很容易理解,试想一下,如果不能保证各个子模块的正确性和完整性,就进行集成测试,必然导致集成之后的产品难以如我们希望地工作。这就好比我们要建造一幢楼房,可是选择了低质量的砖块和楼板,那么这个后果真实难以想象的。同理,对于集成测试之后的产品,再结合设计时候使用的信息,就可以进行确认测试了。既然我们从需求分析、设计、编码一步一步艰难地走过来,现在终于到了测试的时候,为什么还要在集成测试的时候加入设计信息呢?而且,按照图2所示,后面还要加入需求分析?这是因为:我们难免会偏离方向!有可能,在进行详细设计的时候,和最初的需求有所出入,也有可能,我们在编码的时候无意中偏离了总体设计的方案。所以,为了保证软件与客户的需求尽量一致,我们需要一步一步地核实各个阶段的工作。做到项目完整,尽量没有疏漏,满足客户的期望。都不希望在产品交付的时候,客户说:“这不是我想要的。”
图2 软件测试的步骤
传统的软件生命周期分为需求分析、设计、编码、测试和维护几个阶段,这就是著名的“瀑布模型”,它是最简单的一种软件开发过程。软件开发过程模型还有很多中,诸如原型模型、快速应用开发模型、螺旋模型、增量模型和迭代模型、构建组装模型和并发模型等等。这些开发模型并不是本文要讨论的内容。
从本质上来说,软件测试是为了保证软件质量,软件质量就是客户的满意度。从不同的角度,软件测试可以分为不同的种类:
1) 从是否需要执行被测软件的角度,分为静态测试和动态测试。
2) 从测试是否需要针对系统的内部结构和具体实现的算法的角度,分为白盒测试和黑盒测试。
3) 按照测试的对象,分为面向开发的单元测试、GUI和捕捉/回放测试、基于Web的应用测试、负载和性能测试、数据库测试等等。
4) 其他的测试方法还有恢复测试、安全测试、兼容性测试等等。
文献[2]提出了非常详细的测试分类,了解软件测试的分类是重要的,这有助于理解工作内容和沟通团队成员。文献[3]提出了基于渗透、基于故障树的多种软件安全性的测试方法。
2. 单元测试
什么是单元测试?
单元测试的定义有很多种:
• 单元测试是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。
• 通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。
• 执行单元测试,是为了证明某段代码的行为确实和开发者所期望的一致。
单元测试的对象是程序系统中的最小单元-----模块或者称为组件。这个过程是在编码阶段进行的,主要使用白盒测试方法,根据程序的内部结构设计测试用例。多个模块可以并行地测试。所谓最小单元就是有明确的功能定义、性能定义、接口定义,而且可以清晰地与其他的单元区分开来。单元测试通常使用的方法有逻辑结构测试、路径测试、代码走读、静态分析、动态分析等。单元测试的目标是:高内聚、低耦合,确保模块被正确地编码。最终期望的结果是:模块的行为与开发者期望一致。换句话说,这就是开发者对于单元测试的终极目标。当然,作为终极目标,它应该是有依据的,这里的依据就是详细设计说明书。
单元测试的任务:
我们来具体分析一下单元测试的任务,在这个阶段,到底要做什么?为了达到上面说的终极目标,我们需要做以下工作:
1) 模块接口测试。
有数据输入模块,也有数据输出模块,在这个前提下才能进行测试。就是说,要进行测试,首先模块是可以测试的。
2) 模块局部数据结构测试
以保证模块中的数据(也可能是临时存储的),在模块执行的过程中,是完整的、正确的。常见的错误有变量未初始化、地址异常、上溢下溢、类型声明错误等。
3) 模块边界条件测试
就是测试模块中使用的数据的上界和下界,这对于边界测试和设计测试用例是非常重要的。
4) 模块中所有独立执行通路测试
验证每一条路都可执行的,并且每一条路至少执行一次。例如,if判断之后,是执行接下来的又一次判断或者执行语句,还是返回。
5) 模块的各条错误处理通路测试
尽量预见各种出错可能条件,并预设各种错误处理通路。
使用单元测试有什么好处:
• 不但会使工作轻松地完成,而且会使设计变得更好,甚至大大减少花在调试上面的时间。
• 行为与期望一致
• 代码可以依赖
错误和不可以依赖的代码,给接下来的工作会造成很严重的负面影响。软件测试的目的之一就是尽可能早地发现软件中存在的问题,从而降低软件的成本。就这个测试过程来说,单元测试是最基础的,也是测试的第一步,所以它的重要性不言而喻。一个好的单元测试应该做到:自动化、彻底的、可重复、独立的、专业的。也就是:使用工具,不完全依靠人力劳动;不留后患,彻底检查;可以进行多次检测,值得推敲;不依赖其他模块进行本模块的测试;具有一定的专业性。
误区,通常,会有人提出这样的想法:
• 我已经严格按照详细设计的文档进行了编码,为什么要做单元测试?太花时间了。我是严格按照文档做的!
• 我是做开发的,干嘛要做测试?那不是我的工作。
• 我做测试,那测试人员怎么办?
• 我写的这些代码已经编译通过了,没有必要进行测试了
• 反正测试也是测试这个模块,就算正确了,等到系统集成之后,还不知道会发生什么呢。
关于这些问题的解答,可以参考文献[4]。那里面给出了非常详细生动的描述和解释。作者已经读过那本书,在这里不直接给出答案,给读者留些思考的空间。
本文后面叙述的NUnit实践,也是得益于此书介绍的方法,在这里表示非常的感谢!
3. xUnit家族
软件自动化测试的工具有很多种,例如:
• LoadRunner:性能测试、负载测试、压力测试
• QTP:回归测试、测试同一软件的新版本
• Quality Center:基于Web的测试管理工具
• TestDirector:企业级测试管理工具
• Gtest:google的开源C++单元测试框架
• xUnit家族:单元测试
其中的xUnit家族是单元测试框架体系,是一种测试思想与模型的集合。它包含了Junit、CppUnit、 Nunit、 Dunit、PHPUnit、RubyUnit 、ComUnit、HttpUnit等很多语言版本,但是他们的思想是一样的。
xUnit的一般结构有:
• TestFixtures----针对模块的测试代码文件
• TestSuites----组织测试代码的测试套件
• Assertion----断言,用来判断测试的结果
• TestExecution----启动测试的标志
TestResult----测试的结果,详细的测试结果和测试报告
关于测试驱动开发的书籍也有很多,文献[5]是非常经典的一本,它的中译本(文献[6])在国内也是打手欢迎。本文不讨论测试驱动开发,但是测试给开发提供了新的软件开发思路和方法,并且测试驱动开发在部分企业已经开始使用。读者如需了解学习,请参考相关文献,不过,本文列出的文献[5]和文献[6]会是不错的选择哦!
4. NUnit实践
上面说了那么多,现在来个例子吧!
杨辉三角,植物花瓣,黄金比,兔子繁殖 等都涉及到了斐波那契数列,而且这个例子比较简单,那我们就选择斐波那契数列进行实践吧!
声明:本文是用的是NUnit 2.5.9,在本文写作的时候,这个版本是最新的。运行的平台是WindowsXP。如需运行,最好安装Visual Studio 2005,否则会缺运行时库的哦。
图3 运行界面
这个函数是比较简单的,按照定义,可以很容易地得到代码,编写函数如下:
int Fib(int n)
{
if (n<0)
{
return 0;
}
if (n == 0)
{
return 0;
}
if (n==1)
{
return 1;
}
return Fib(n - 1) + Fib(n - 2);
|
|
现在我们进行测试:
1、 f(0)=0,通过。测试效果如右图:
[Test]
public void testFib1()
{
Assert.AreEqual(0, Fib(0));
|
2、 f(1)=1,通过。测试效果如右图:
[Test]
public void testFib2()
{
Assert.AreEqual(1, Fib(1));
}
3、 n=2,3,4,5,……等等一系列的值,测试之后,都通过了
4、 n的最小值为0,如果是负数呢?根据函数的定义及属性,可以假设f(-1)=0。
5、 不管n取值多少,f(n)的最大值是int型变量的最大值。据此,我们需要n的上限值。
6、 如果n的输入值为浮点数,程序会怎样运行呢?
诸如这些遗留问题,都需要单元测试。从我们手里提交的代码,最好必要给别人的工作造成负担,所以单元测试肩负着使代码可靠的重任。
5. 小结
关于单元测试还有很多,包括静态测试、动态测试、调试与评估等。相关的领域还有极限编程(XP),测试驱动开发TDD(说过的),JUnit(它可是NUnit的基础),面向测试的设计(作者刚听说的),Mock对象,驱动模块,桩模块,版本控制CVS等等。这些都有待于实践和感受,在这里干说是用处不大的。
什么时候结束单元测试呢?当软件的单元功能、单元接口与设计需求一致,对于输入能够给出准确的响应,能够容忍一定的错误,能够处理异常,还有,完成单元测试报告。当这些都满足之后,就可以说,本模块完成了。当然,本文也快要结束了。别忘了,高可靠性的模块是组成可靠系统的坚实基础。
参考文献
[1]张海藩.软件工程.北京:人民邮电出版社,2006.1.P6-P8
[2]朱少民.软件测试方法和技术.北京:清华大学出版,2005.7.P37-P38
[3]吕金和. 软件安全性测试研究.计算机安全.2010.8
[4](美)托马斯等著,陈伟桩,陶文 译.单元测试之道C#版:使用NUnit-----程序员修炼三部曲第二部. 电子工业出版社:北京,2005年01月.P7-P12
[5] Kent Beck. Test-Driven Development By Example. Addison Wesley: November 08, 2002
[6](美) Kent Beck著,孙平平,张小龙,赵辉等译,崔凯校.测试驱动开发(中文版).北京:中国电力出版社.2004