软件测试 第二章 单体测试.docx
《软件测试 第二章 单体测试.docx》由会员分享,可在线阅读,更多相关《软件测试 第二章 单体测试.docx(25页珍藏版)》请在冰点文库上搜索。
![软件测试 第二章 单体测试.docx](https://file1.bingdoc.com/fileroot1/2023-6/17/82de24b2-8f7e-45bb-a29f-af84632d9e40/82de24b2-8f7e-45bb-a29f-af84632d9e401.gif)
软件测试第二章单体测试
第2章单元测试
本章目标
Ø能够应用JUnit进行单元测试
Ø掌握JUnit+EclEmma进行覆盖率测试
本章单词:
unit__________________________cover_________________________
emma__________________________assert________________________
equals________________________same__________________________
plugin________________________feature_______________________
4.1单元测试简介
单元测试就是测试程序员依据其所设想的方式开发出来的程序是否产生了预期的结果。
单元测试是与软件开发生命周期中的编码阶段结合起来的,并且只有项目具备各个窗体、报表和屏幕之后才能开始进行测试。
编码阶段的基本单元式:
程序、函数、过程、窗体、或者图形用户界面。
在单元测试中,需要测试以下内容:
代码的控制流程、给函数传递参数值、从函数获取值是否正确。
另外,单元测试不只限于检查控制流程或者程序的执行,还检查代码是否遵循编码标准。
在对每个模块进行单元测试时,不能完全忽视它们和周围模块的相互关系。
为模拟这一联系,在进行测试时,需设置若干辅助测试模块。
辅助模块有两种,一种是驱动模块,用以模拟被测试模块的上级模块。
驱动模块在单元测试中接收测试数据,把相关的数据传送给被测模块,启动被测模块,并打印出相应的结果。
另一种是桩模块,用以模拟被测试模块工作过程中所调用的模块。
桩模块由被测模块调用,它们一般只进行很少的数据处理,例如打印入口和返回,以便于检查被测试模块与其下级模块的接口。
测试中通常使用测试驱动模块和桩模块。
测试驱动程序是帮助执行软件以检查其是否工作的软件。
此程序提供一个框架来设置参数、执行单元并记录结果。
桩模块不是实际单元而是模型,用以取代实际单元并帮助执行测试过程。
开发人员将测试作为编程的必要过程,并采用单元测试来验证它们的代码是否执行预期的功能。
开放源码的单元测试框架有JUnit和Nunit。
JUnit适用于Java应用程序,它是由SmallTalk的Sunit派生而来。
Nunit用于测试.NET程序。
PHPUnit是一个轻量级的PHP测试框架。
JavaScript有JsUnit,数据库有DBUnit……
4.2JUnit
4.2.1JUnit简介
JUnit是由ErichGamma和KentBeck编写的一个回归测试框架。
JUnit测试是程序员测试,即所谓白盒测试,因为程序员知道被测试的软件如何(How)完成功能和完成什么样(What)的功能。
JUnit是一套框架,继承TestCase类,就可以用JUnit进行自动测试了。
JUnit是一个开放源代码的Java测试框架,用于编写和运行可重复的测试。
它是用于单元测试框架体系xUnit的一个实例(用于java语言)。
它包括以下特性:
用于测试期望结果的断言(Assertion)。
用于共享共同测试数据的测试工具。
用于方便的组织和运行测试的测试套件。
图形和文本的测试运行器。
另外JUnit是在xp编程(ExtremeProgramming)和重构(refactor)中被极力推荐使用的工具,因为在实现自动单元测试的情况下可以大大的提高开发的效率,但是实际上编写测试代码也是需要耗费很多的时间和精力的,那么使用这JUnit好处到底在哪里呢?
有以下几点供参考:
A、对于xp编程而言
要求在编写代码之前先写测试,这样可以强制你在写代码之前好好的思考代码(方法)的功能和逻辑,否则编写的代码很不稳定,那么你需要同时维护测试代码和实际代码,这个工作量就会大大增加。
因此在xp编程中,基本过程是这样的:
构思→编写测试代码→编写代码→测试,而且编写测试和编写代码都是增量式的,写一点测一点,在编写以后的代码中如果发现问题可以较快的追踪到问题的原因,减小回归错误的纠错难度。
B、对于重构而言
其好处和xp编程中是类似的,因为重构也是要求改一点测一点,减少回归错误造成的时间消耗。
C、对于非以上两种情况
我们在开发的时候使用JUnit写一些适当的测试也是有必要的,因为一般我们也是需要编写测试的代码的,可能原来不是使用的JUnit,如果使用JUnit,而且针对接口(方法)编写测试代码会减少以后的维护工作,例如以后对方法内部的修改(这个就是相当于重构的工作了)。
另外就是因为JUnit有断言功能,如果测试结果不通过会告诉我们哪个测试不通过,是什么原因。
而不是写一些测试代码看其输出结果,然后再由自己来判断结果使用正确,使用JUnit的好处就是这个结果是否正确的判断是它来完成的,我们只需要看看它告诉我们结果是否正确就可以了,在一般情况下会大大提高效率。
在测试代码时,必须遵守特定的命名惯例,待测的每种方法的命名应该testXXXX()的格式。
任何编写的测试代码应执行并完成以下任务:
设置测试所需要的所有条件。
调用待测的方法。
验证待测的方法是否执行预期的功能。
清除。
测试代码的编写方式与程序代码相同。
除了在测试代码中会用到附加库以外,其他的与程序代码相同。
在执行的时候,执行测试代码而不是实际的项目代码。
测试代码在一定程度上控制条件下检查项目代码。
JUnit提供一些测试方法,用于判断正在测试的特定方法是否正确执行那个功能。
这些方法被称为asserts(),它断言某个特定条件是否为真。
断言是单元测试中的基本结构。
如表4-1所示,JUnit提供了多个assert()方法,这些方法包含在junit.framework.TestCase类中。
表4-1JUnit中的断言
方法
功能描述
assertEquals
判断给定两对象的值是否相等
assertNull
断言给定对象是否为null
assertNotNull
断言给定对象是否不为null
assertSame
判断给定两对象是否指同一对象
assertNotSame
判断给定两对象是否不是指同一对象
assertTrue
断言给定的布尔条件是否为真
assertFalse
断言给定的布尔条件是否为假
fail
测试失败
断言失败时,将终止测试方法,并引发AssertionFailedError。
4.2.2添加JUnit支持
无论是Eclipse还是MyEclipse都自带有JUnit,我们只需添加JUnit支持即可,想了解更多关于JUnit请参考官方网站www.junit.org。
下面我们介绍如何在MyEclipse中添加JUnit支持。
1、在PackageExplorer中选中要添加JUnit支持的项目,点击右键。
图2-1选中项目,点击右键
2、选择BuildPath,然后再选择AddLiraries。
图2-2AddLibraries
3、选择JUnit,然后点击Next按钮。
图2-3选择JUnit
4、选择JUnit版本(推荐JUnit3),但后点击Finish。
图2-4选择版本为JUnit3
5、此时项目里就已经引用了JUnit.jar。
图2-5添加JUnit之后的效果
上述添加支持的目的就是为了导入junit.jar,除此之外,还可以直接复制junit.jar到项目的lib目录下。
4.2.3第一个JUnit程序
现在有一个类JunitDemo.java文件,需要对addNum方法做测试。
例4-1:
JunitDemo.java
packagecom.slrj.dean.biz;
publicclassJUnitDemo{
publicintaddNum(intnum1,intnum2){
returnnum1+num2;
}
}
实现步骤如下:
新建一个类JunitDemoTest.java。
导入junit.framework.TestCase包。
新建JunitDemo对象引用。
新建测试addNum的方法,命名为testAddNum。
利用assertEquals进行断言。
例4-2:
JUnitDemoTest.java
packagecom.slrj.dean.biz;
importjunit.framework.TestCase;
publicclassJUnitDemoTestextendsTestCase{
JUnitDemodemo=newJUnitDemo();
publicvoidtestAddNum(){
assertEquals(demo.addNum(10,10),20);
}
}
1、以JUnitTest方式进行运行。
图2-6以JUnittest运行
2、运行结果如图。
图2-7运行结果
在上面的JUnitDemoTest中需要大家注意的是,测试类必须继承自TestCase这个父类,每个测试方法必须以test作为前缀开头,运行测试类时需要以JUnitTest方式进行运行。
4.3逻辑驱动覆盖测试
逻辑驱动覆盖测试是传统的白盒测试方法。
该方法利用程序内部逻辑结构设计测试用例,以期达到覆盖程序中所有的路径的目的,它包括:
语句覆盖、判断覆盖、条件覆盖、判定条件覆盖、条件组合覆盖、路径覆盖。
某学校自动化管理系统中有如下方法,其中细节代码已经省略,主题结构如下:
例4-3:
getClassInfo方法
publicStringgetClassInfo(intclassId,intgradeId,intschoolId){
Stringresult="";
//入口……
if(classId>3&&schoolId<10){
//语句块1……
}
if(classId==4||gradeId>5){
//语句块2……
}
//出口……
returnresult;
}
上面getClassInfo方法的流程图为:
图2-8例子流程图
为了说明简略,分别对各个判断的取真、取假分支编号为A、B、C、D。
4.3.1语句覆盖
语句覆盖就是设计若干个测试用例,运行被测试程序,使得每一条可执行语句至少执行一次。
根据概念,为了对上面的函数进行语句覆盖,只要设计一个测试用例就可以把三个执行语句块中的语句覆盖。
针对程序的判定语句,在入口处设计测试用例。
测试用例输入为:
{classId=4、gradeId=5、schoolId=5}
程序执行的路径是:
AC
如果程序只运行上面测试用例,可以看到模块中的所有语句都被执行了,但不能检查判断逻辑是否有问题,例如在第一个判断中错误的把&&改写成||,则上面的测试用例仍可以覆盖所有的执行语句。
可以说语句覆盖是最弱的逻辑覆盖准则。
4.3.2判定覆盖
设计若干个测试用例,运行所测程序,使程序中每个判断的取真分支和取假分支至少执行一次;根据上面的定义,对于上面的程序,如果设计两个测试用例则可以满足条件覆盖的要求。
测试用例的输入为:
{classId=4、gradeId=5、schoolId=5}
{classId=2、gradeId=5、schoolId=5}
上面的两个测试用例虽然能够满足条件覆盖的要求,但是也不能对判断条件进行检查,例如把第二个条件gradeId>5错误的改写gradeId<5,上面的测试用例同样满足了分支覆盖。
4.3.3条件覆盖
设计足够多的测试用例,运行所测试程序,使程序中每个判断的每个条件的每个可能取值至少执行一次。
为了设计测试用例清楚,对例子中的所有取值加以标记。
对第一个条件判断:
条件classId>3取真值为T1,取假为-T1
条件schoolId<10取真值为T2,取假为-T2
对第二个条件判断:
条件classId=4取真值为T3,取假值为-T3
条件gradeId>5取真值为T4,取假值为-T4
则可以设计测试用例如下:
表4-2条件覆盖
测试用例
覆盖分支
条件取值
classId=4,gradeId=5,schoolId=5
AC
T1、T2、T3、T4
classId=2,gradeId=5,schoolId=5
BD
-T1、T2、-T3、-T4
classId=4,gradeId=5,schoolId=15
BC
T1、-T2、T3、-T4
上面的测试用例不但覆盖了所有分支的真假两个分支,而且覆盖了判断中的所有条件的可能值。
但是如果设计了下面的测试用例,则虽然满足了条件覆盖,但只覆盖了第二个条件的取真分支,不满足分支覆盖的要求。
4.3.4判定—条件覆盖
设计足够多的测试用例,运行所测试程序,是程序中每个判断的每个条件的所有可能取值至少执行一次,并且每个可能的判断结果也至少执行一次,换句话说,即要求各个判断的所有可能条件取值组合至少执行一次。
根据定义只需设计两个测试用例便可以覆盖8个条件值及4个判断分支。
表4-3判定-条件覆盖
测试用例
覆盖分支
条件取值
classId=4,gradeId=6,schoolId=5
AC
T1、T2、T3、T4
classId=2,gradeId=5,schoolId=15
BD
-T1、-T2、-T3、-T4
判定条件覆盖从表面来看,它测试了所有条件的取值,但是实际上某些条件掩盖了另一些条件。
例如对于条件表达式(classId>3)&&(schoolId<10)来说,必须两个条件都满足才能确定表达式为真。
如果(classId>3)为假则一般的编译器不在判断是否在schoolId<10了。
对于第二个表达式(classId-4)||(gradeId>5)来说,若classId==4测试结果为真,就认为表达式的结果为真,这是不再检查(gradeId>5)条件了。
因此采用判定条件覆盖,逻辑表达式中的错误不一定能够查出来了。
4.3.5条件组合测试
设计足够多的测试用例,运行所测程序,使程序中每个判断的所有可能条件取值组合至少执行一次。
现在对例子中的各个判断的条件取值组合加以标记如下:
1、classId>3,schoolId<10记做T1T2,第一个判断的取真分支
2、classId>3,schoolId>=10记做T1-T2,第一个判断的取假分支
3、classId<=3,schoolId<0记做-T1T2,第一个判断的取假分支
4、classId<=3,schoolId>=10记做-T1-T2,第一个判断的取假分支
5、classId=4,gradeId>5记做T3T4,第一个判断的取真分支
6、classId=4,gradeId<=5记做T3-T4,第一个判断的取真分支
7、classId!
=4,gradeId>5记做-T3T4,第一个判断的取真分支
8、classId!
=4,gradeId<=5记做-T3-T4,第一个判断的取真分支
根据定义取4个测试用例,就可以覆盖上面8种条件取值的组合。
测试用例如下表:
表4-4条件组合测试
测试用例
覆盖分支
条件取值
覆盖组合号
classId=4,gradeId=6,schoolId=5
AC
T1、T2、T3、T4
1和5
classId=4,gradeId=5,schoolId=15
BC
T1、-T2、T3、-T4
2和6
classId=2,gradeId=6,schoolId=5
BC
-T1、-T2、-T3、T4
3和7
classId=2,gradeId=6,schoolId=15
BD
-T1、-T2、-T3、-T4
4和8
上面的测试用例覆盖了所有条件的可能取值的组合,覆盖了所有判断的可取分支,但是却丢失了一条路径。
4.3.6路径测试
路径测试就是设计足够多的测试用例,覆盖被测试对象中的所有可能路径。
在上面的测试用例中修改一个测试用例则可对程序进行全部的路径覆盖。
表4-5路径测试
测试用例
覆盖分支
条件取值
classId=4,gradeId=6,schoolId=5
AC
T1、T2、T3、T4
classId=4,gradeId=5,schoolId=15
BC
T1、-T2、T3、-T4
classId=5,gradeId=4,schoolId=5
AD
T1、T2、-T3、-T4
classId=5,gradeId=6,schoolId=15
BD
T1、-T2、-T3、-T4
4.4JUnit+EclEmma进行覆盖测试
4.4.1EclEmma简介
现在IT开发人员比以往任何时候都更加关注测试的重要性,没有经过良好测试的代码更容易出问题。
在极限编程中,测试驱动开发已经被证明是一种有效提高软件质量的方法。
在测试驱动的开发方式中,软件工程师在编写功能代码之前首先编写测试代码,这样能从最开始保证程序代码的正确性,并且能够在程序的每次演进时进行自动的回归测试。
测试对于软件产品的成败起着至关重要的作用,在极限编程领域,甚至有人提议任何未经测试的代码都应该自动从发布的产品中删除。
作者并不确信这个观点是正确的,但是测试本身的质量确实是一个需要高度关注的问题。
测试的覆盖率是测试质量的一个重要指标,我们需要工具来帮助我们进行对软件测试覆盖的考察。
EclEmma就是这样一个能帮助开发人员考察测试覆盖率的优秀的Eclipse开源插件。
EclEmma在覆盖测试领域是如此的优秀,以致于它在过去不久的2006年成为了EclipseCommunityAwardsWinners决赛选手。
虽然最后EclipseCheckstylePlugin取得了BestOpenSourceEclipse-basedDevelopertool的称号,但我们也可以由此看到EclEmma对开发人员的帮助是巨大的(EclipseCommunityAward的具体信息可以参阅参考资源)。
提到EclEmma首先就要说到著名的Java覆盖测试工具Emma。
Emma是一个在SourceForge上进行的开源项目(参阅参考资源)。
从某种程度上说,EclEmma可以看作是Emma的一个图形界面。
在本文的参考文献中,可以看到专门讲述使用Emma的技术文章。
Emma的作者开发Emma之初,程序员已经有了各种各样优秀的开源Java开发工具。
举例来说,我们有优秀的集成开发环境Eclipse,有开源的JDK,有单元测试工具JUnit,有Ant这样的项目管理工具,还可以用CVS或SubVersion来进行源代码版本的维护。
当时看来,也许唯一缺少的就是一个开源的覆盖测试工具了。
Emma就是为了填补这项空白而生的。
现在的情况已经和Emma诞生的时候不一样的。
时至今日,已经有了不少的覆盖测试工具。
例如Coverlipse是一个基于Eclipse的覆盖测试插件。
其他还有Cobertura、Quilt和JCoverage等。
但是Emma具有一些非常优秀的特性使得它更适合被广泛的使用。
和Coverlipse等工具比起来,Emma是开源的,同时它对应用程序执行速度的影响非常小。
EclEmma的出现弥补了Emma用户一个大的遗憾——缺乏图形界面以及对集成开发环境的支持。
将Eclipse和Emma这两个在各自领域最为优秀的工具结合起来,这就是EclEmma为我们提供的。
接下来,一起看看EclEmma为开发人员提供了什么。
4.4.2添加EclEmma支持
在官方网站下载EclEmma压缩包。
图2-9EclEmma压缩包
解压eclemma-1.4.3.zip文件,得到plugins文件夹和features文件夹。
图2-10解压ZIP文件
将plugins里面所有jar文件复制到Myeclipse的eclipse\plugins目录下;将features里面文件夹复制到eclipse\features目录下。
运行MyEclipse(如果已经运行,请重启MyEclipse),即可集成EclEmma成功。
4.4.2生成测试覆盖率报告
在工作区域中右键,点击CoverageAs→JUnitTest。
图2-11CoverageAs
运行后覆盖率的结果。
图2-12运行结果
在Coverage视图中,点击项目名→右键→选择ExportReport。
图2-13选择ExportReport
Format选择默认的HTMLfiles,Destination填写为生成报告的路径,然后点击Finish。
图2-14选择目标文件
系统会自动生成报表文件,如图2-15;运行QTPLogin_JUnitDemo.html文件,点击com.slrj.dean.biz超链接,会看到详细覆盖率报告,如图2-17。
图2-15报表文件
图2-16点击超链接
图2-17详细覆盖率报告
作业
一、简答题
1、使用JUnit测试下面的类
/**
*******************************************************************************
*DateUtil.java
*
*(c)Copyright2009YangYan
*
*<所属工程名>
*Test(事例工程)
*<功能概要>
*日期工具类
*<作者>
*杨砚
*******************************************************************************
*/
packagemon;
importjava.util.regex.Matcher;
importjava.util.regex.Pattern;
/**
*日期工具类
*
*
*@author杨砚
*/
publicclassDateUtil{
/**
*判断一个日期字符串是否合法,日期格式为xxxx-xx-xx
*
*
*@paramdateString输入需要判断的日期字符串
*@returntrue:
合法false:
不合法
*/
publicbooleanvalidate(StringdateString){
//通过正则表达式首先判断日期格式是否符合xxxx-xx-xx
Patternp=Ppile("\\d{4}+[-]\\d{1,2}+[-]\\d{1,2}+");
Matcherm=p.matcher(dateString);
if(!
m.matches()){
returnf