`
universsky
  • 浏览: 99340 次
文章分类
社区版块
存档分类
最新评论

Java单元测试技术1

 
阅读更多

摘要本文针对当前业软开发现状,先分析了WEB开发的技术特点和单元测试要解决的问题,然后分别阐述了解决这些问题的单元测试技术,内容包括:JUnit、测试桩构建、访问数据库的Java代码测试、Struts框架测试、服务器布署环境下的组件测试、Spring下的单元测试,以及覆盖率检查技术,最后还谈到了测试自动化技术以及希望在业软推广的自动化测试框架和它带来的好处。另外,随本文还附有例子代码供大家参考。

关键词:JavaWeb开发、单元测试、工具、JUnitEasyMockDBUnitStrutsStrutsTestCaseCactusSpringCobertura、覆盖率检查、自动化测试、例子代码。

单元测试与开发技术密切相关,业软基于Java的开发一般是WEB应用开发,涉及的开发技术繁多,尤其是现在的开源软件盛行,更给Java增添了无穷的活力和生机,同时也给单元测试增加了复杂度,我们推行单元测试面临着前所未有的挑战,难怪项目组抱怨单元测试难测或测不起来。本文试图给出基于业软开发现状的Java单元测试完整解决方案,以便更加有效地在业软推行单元测试。下面我们从分析开发技术特点以及对单元测试的影响说起。

1 基于Java开发的技术特点

u 容器管理的组件开发

开发WEB应用程序,实质上就是在开发一系列组件。组件的类型有很多,JavaBeanServletFilterJSP TaglibEJBSpring Bean,等等。这些组件一般是不能独立运行的,需要将它们布署到WEB服务器,通过与WEB容器或EJB容器交互才能实现一定的业务逻辑,也就是说,组件依赖的许多对象是运行时由容器创建的,如HttpServletRequestHttpServletResponseServletContextSessionContextFilterChainPageContext,等等,这就面临着单元测试时这些容器对象如何生成的问题。对于这些组件的测试一般我们有两种单元测试方法:一种是对被测组件进行隔离测试,组件依赖的服务器环境对象用桩取代,它的缺点是构建太麻烦;另一种是将组件运行在真实服务器环境下,有别于系统测试,被测对象是我们主动在测试代码中创建的,它的优点是更接近于真实环境、免除了构建的工作量。

u 页面显示的视图开发

有很多技术用于将视图与业务逻辑分开,如StrutsJSFWebWorkSpring等,页面开发的技术也有很多,如HtmlXMLJSPJavaScriptVelocity,等等,对于这些页面文件,技术上很难进行编译静态检查,对它们的测试虽然有一些工具支持,但效果均不理想,而且代码Review大家反映也很难发现实质性的页面问题,如何确保页面文件的质量一直是单元测试要解决的问题。

u 框架复杂

WEB开发使用的框架很少有自己独立设计的,一般都会使用现成的架构,如StrutsJSFWebWorkEJBSpringHibernateiBATIS等等,不同的框架,能够支持单元测试的程度也是不一样的。

u 数据库访问技术多

业软的WEB应用开发很少有不访问数据库的,因为多是面向业务的开发,业务离不开数据存储。项目组可能会选择不同的数据库访问技术,如JDBCEntityBeanSpringHibernateiBATIS等,对于涉及数据库访问的代码如何做单元测试也是我们需要解决的问题。

2 单元测试技术需要解决的问题

有许多的单元测试技术和工具,综合起来,无非是为了解决以下问题。

u 驱动(Driver)—驱动被测单元

单元不能独立运行,必须实现调用它们的代码,我们称其为驱动代码,其实最简单的驱动就是实现main方法,大家常用的驱动典型工具就是JUnit

u 构建桩(Stub)—模拟被测单元依赖的对象

被测的孤立单元通常会对其它对象有依赖,这种依赖通常表现在:依赖对象通过被测方法参数传入或者被测对象保存有依赖的对象引用,然后在被测方法中调用了依赖对象的方法。构造依赖对象,一方面我们可以直接将开发完成的并且之前已经过单元测试的代码直接拿过来用,另一方面,也是更常用的方法,就是自己构建模拟对象,我们称其为桩,但自己写桩很麻烦,工作量大,现成构建桩的理想工具是EasyMock

u 验证(Verify

用例执行是否成功,需要在测试中添加验证点,需要将预期结果与测试执行获得的实际结果进行比较,为此JUnit为我们提供了验证的基本逻辑框架,其它工具可以基于它实现更复杂的验证逻辑,如DBUnit实现的对数据库表数据的验证。

u 用例管理

常有同事提到用main方法也能实现对被测单元的驱动,但我觉得最大的不足是无法实现对用例的有效管理,为此JUnit为我们提供了用例管理的基础框架,通过引入测试套的概念将用例有效地组织起来。

u 结果输出(Report

测试结束后要能够将本次运行的结果情况形成报告,并以图形化直观的形式报告给用户。JUnit也为我们做到了,尤其是IDEJUnit的集成,使我们在开发过程中做单元测试变得更加方便。

u 覆盖率检查

公司要求被测代码要求达到语句的100%覆盖,是否覆盖到了,我们可以借助覆盖率检查工具,做测试执行的同时进行覆盖率检查,对未覆盖到的代码可能会发现两类问题:不可达代码,这样的代码需要优化;用例设计不充分,这时就要及时补充用例。常用的覆盖率检查工具有PureCoverageCobertura

u 测试自动化

实现测试自动化的好处大家都很明白,方便回归测试,节省了工作量;另一个好处是便于对测试的监控,这一点我们在后面会谈到。我们模索系统测试自动化已有多年,但效果都不理想,主要原因我觉得和系统测试本身的特点有关,因为系统测试是站在用户角度看系统功能的整体表现(这其中最讨厌的是我们经常还有需求变更,如何做到以不变应万变,我们曾经尝试过,但效果均不理想)。但单元测试不同于系统测试,单元不能独立运行,需要我们实现驱动代码,它的这个特点决定了实现单元测试自动化是非常容易也是顺理成章的事。

3 Java单元测试技术总览

基于Java的开发一般分为Java应用程序开发Web应用程序开发,目前我们已经有了针对Java开发的较为全面的单元测试解决方案,可以说,每个开发领域都已经有了相应的单元测试技术支持,以下是一个简单示意图,后面的章节我们针对每项技术分别阐述。

4 单元测试的基础框架(JUnit

JUnitJava单元测试的经典之作,它的功能包括:提供实现测试用例的框架,并驱动测试执行;提供验证逻辑;用例执行结果统计与报告;测试套、测试用例管理。JUnitJava单元测试的基础框架,Java领域的其它测试工具和技术一般都是基于它的扩展,如我们后面提到的:EasyMockStrutsTestCaseDBUnit等,另外Spring中的单元测试支持也是基于它的。

使用JUnit只需要将junit.jar加入CLASSPATH路径即可。因为Java编译后不需要链接(不同于C++),加上流行的IDE,如EclipseJBuilderNetBeans等,都集成有JUnit,我们可以在IDE中边写代码、边做测试、边做重构。所以实际上,Java的单元测试比起C++来更方便。

1) 基本概念

测试用例(TestCase:同我们公司的测试用例概念,但用例是以测试方法(测试代码)的形式体现的,一个测试方法对应一个测试用例,JUnit框架提供了抽象类TestCase,我们要做的就是继承该类,增加测试方法,在测试方法中实现对被测试代码的调用,并增加验证点。同一个类可以有很多个测试方法,你只要向测试框架提供这个实现类就可以了,框架负责生成测试用例对象(就是实例化测试用例),一个测试方法生成一个测试用例对象,用例有用例名称,取的就是测试方法名。测试方法的原型(函数定义)必须满足以下要求:

ü 当然是公共方法

ü 方法名以“test”开头

ü 方法无参数

ü 方法的返回值类型为void

框架在装载你的测试用例类的时候,使用JAVA的反射技术,基于以上条件找到你的测试方法,并创建用例对象。

测试套(TestSuite:若干个测试用例对象组成一个测试套,测试套与测试用例的关系就如同文件夹与文件的关系一样,测试套包含测试用例,当然也可以包含子测试套。之所以这样,是便于测试用例的组织和管理。JUnit提供了可直接实例化的TestSuite类,实现了很多功能,例如:分析一个测试用例类,对每一个测试方法生成一个测试用例对象,将这个测试用例类的所有测试用例对象作为一个测试套;实现对测试用例的调用以及子测试套的递归调用(实际上是子测试套包括的用例)。

2) 测试用例类的基本结构

以下是测试用例类的基本结构:

public class HelloWorldTest extends TestCase

{

/*

* 每次用例执行前要执行的初始化方法

*/

protected void setUp( ) throws Exception

{

super.setUp();

}

/*

* 每次用例执行后要执行的清除功能

*/

protected void tearDown( ) throws Exception

{

super.tearDown();

}

/*

* 一个测试方法,在其中实现对被测单元的调用,并验证

*/

public final void testCalculate( )

{

//TODO 实现 calculate()

}

}

其中TestCase基类由JUnit框架提供。

3) 框架常见类介绍

JUnit包含6个包(package):junit.awtuijunit.swinguijunit.textuijunit.extensionsjunit.frameworkjunit.runner。其中前三个包中包含了JUnit运行时的入口程序以及运行结果显示界面,它们对于JUnit使用者来说基本是透明的。junit.runner包中包含了支持单元测试运行的一些基础类以及自己的类加载器,它对于JUnit使用者来说是完全透明的。剩下的两个包是和使用JUnit进行单元测试紧密联系在一起的。其中junit.framework包含有编写一般JUnit单元测试类必须要用到的JUnit类;而junit.extensions则是对framework包在功能上的一些必要扩展以及为更多的功能扩展留下的接口。我们常用的还是junit.framework包,所以下面我就将这个包下的类作一介绍。

以上是这个包下的类图,为方便理解,我将Throwable类和Error类也放了进来,注意它们是JDK提供的类,非本包的类。

u 接口Test、抽象类TestCase、类TestSuite之间的关系

Test是接口(interface),定义有方法runTestCaseTestSuite都实现了该接口,前面我们提到TestSuite包含TestCase TestSuite有一个Vector成员,Vector的元素就是它包含的TestCase,当然也可以是子TestSuite,所以为便于管理,TestSuite只认Test接口,在它看来TestSuite包含的是Test对象,而不用细分为TestCase和子TestSuite。对测试套run方法的调用,相应地就会递归调用它所包含的用例run方法和子测试套run方法。这里使用到了Composite设计模式,另外,从命令调用(run命令)与命令实现分离的角度看又能看到Command设计模式的影子。

u 抽象类TestCase

抽象类TestCase实现了接口Test,以下是接口Test的一个方法原型:

public abstract void run(TestResult result);

TestCase对这个方法的实现如下:

public void run(TestResult result) {

result.run(this);

}

对这个方法的调用又将主动权交给TestResult,由TestResult来执行对被测方法的调用,这样便于TestResult跟踪用例执行结果并记录下来,以便作最后的统计分析。

转到TestResult中这个方法的实现:

protected void run(final TestCase test) {

startTest(test);

Protectable p= new Protectable() {

public void protect() throws Throwable {

test.runBare();

}

};

runProtected(test, p);

endTest(test);

//这个方法和上面的startTest用于调用TestListener接口对象,实现监听机制。

}

在这个方法中调用了TestCaserunBare方法:

public void runBare() throws Throwable {

setUp();

try {

runTest();

}

finally {

tearDown();

}

}

可以看到先调用了setUp,之后不管是否产生了异常,都一律调用了tearDown,这两个方法在TestCase中空实现,我们的用例子类可以覆盖(overload)这两个方法,用于用例执行前的初始环境建立和用例执行后的环境清除。接下来我们关注TestCaserunTest方法,看它如何调用到我们的测试方法的。前面提到,TestCase维护了一个用例名称,这个用例名称在TestCase实例化时传入,用例名称就是测试方法名,知道了测试方法名,也就知道了测试方法原型,使用JAVA的反射机制实现了对测试方法的调用。注意这个测试方法正是TestCase子类需要实现的。

protected void runTest() throws Throwable {

......

Method runMethod= null;

try {

runMethod= getClass().getMethod(fName, null);// fName是测试方法名

} catch (NoSuchMethodException e) {

......

}

……

try {

runMethod.invoke(this, new Class[0]);

}

......

}

u TestSuite

TestSuite不需要我们再子类化了,它维护了对一组测试用例对象的引用(包括它所包含的嵌套TestSuite对象),它也实现了Test的接口,run方法的实现就是递归调用它所引用的对象的run方法,下面我们谈一下给定一个TestCase子类(指类定义),TestSuite是如何针对其中定义的每个测试方法生成对应的TestCase对象的。以下是TestSuite的两个相关方法,一个是构造方法(给定用例类,生成该测试套下的用例对象)和成员方法(给定用例类,生成子测试套下的用例对象)。

public TestSuite(final Class theClass){ }

public void addTestSuite(Class testClass) { }

这两个方法的实现相似,基本实现思路是根据JAVA的反射技术,找出用例定义的满足以下条件的成员方法(测试方法)

条件1:当然是公共方法

条件2:方法名以“test”开头

条件3:方法无参数

条件4:方法的返回值类型为void

对应每一个这样的方法生成该用例类的一个对象,并将用例名初始化为该方法名。测试套下的用例对象就是这样生成。

u Assert

AssertTestCase的基类,其中定义了可供TestCase使用的大量静态方法,用于实际返回值与预期值的比较,这些方法名都是以assert开头,在我们自己写的测试方法中可以使用这些方法用于测试结果的验证。要注意的是JUnit对测试失败(实际值与预期值不符)是以抛出AssertionFailedError异常的形式体现的。我们看一个assert方法的典型实现:

static public void assertTrue(String message, boolean condition) {

if (!condition)

fail(message);

}

static public void fail(String message) {

throw new AssertionFailedError(message);

}

一旦验证逻辑验证失败,该用例就会抛出AssertionFailedError异常,TestResult记录下测试失败的该用例对象引用以及它所抛出的异常,这两者的对应关系是TestFailure类体现的,它用于维护用例对象与异常(注意异常的引用类型是所有异常的基类Throwable)的对应关系。TestResult有一个Vector类型成员变量fFailures,它的元素就是TestFailure对象。

用例执行中除了可能会捕获到AssertionFailedError异常,被测代码有可能会抛出其它异常,包括RuntimeException运行期异常、JAVA虚拟机抛出的Error异常,抛出了这样的异常,也意味着用例执行失败,为此TestResult还有另一个Vector类型成员变量fErrors,它的元素也是TestFailure对象,不过它维护的是用例对象与非AssertionFailedError异常的对应关系。

在用例执行结果中分别显示出因抛出AssertionFailedError异常而失败的用例个数(故障,Failure),以及因抛出非AssertionFailedError异常而失败的用例个数(错误,Error)。

以下是在Eclipse中的的执行结果图示:

4) JUnit4.X的用例设计

JUnit4.0之后的版本在用例设计上有较大的改变,主要使用了JDK5.0Annotation新特性,但业软有相当多的版本还未升级到JDK5.0,另外,部分基于JUnit的单元测试工具还未升级到JUnit4.X,所以短期内就会出现JUnit新老版本共存的局面,但不管如何,JUnit中设计用例都很方便。下面我们简单看一下JUnit4.0以后的用例设计。

u 用例类不再需要继承自TestCase基类

u 测试方法名也不需要必须以test开头

u 几个Annotation标识

Ø @Test—标识一个测试方法(对应一个用例)

l expected属性可以标识该方法执行期望会抛出什么异常

l timeout属性可以指定该方法必须在指定的时间内执行结束

l 验证失败抛出JDK自带的AssertionError异常

Ø @Before —每个测试方法运行前要执行的方法,相关于以前的setUp()

Ø @After —每个测试方法运行后要执行的方法,相关于以前的tearDown()

Ø @Ignore —暂时不运行该测试方法

另外,新版本的EclipseANT已经全面支持JUnit4.X

分享到:
评论

相关推荐

    Java平台的单元测试技术研究.pdf

    Java平台的单元测试技术研究关注于提升应用程序的稳定性和质量,随着Java技术在中国社会的重视程度不断提升,单元测试成为保障系统质量的关键技术。研究内容主要包括单元测试技术的使用流程、测试工作的相关准则、...

    java单元测试demo

    总结来说,这个"java单元测试demo"是关于如何使用JUnit进行接口的功能测试,通过编写测试用例、设置断言以及可能的模拟技术,确保接口的输入和输出符合预期,从而提升代码质量和可维护性。通过运行测试并查看结果,...

    java单元测试篇:使用clover为java代码的junit测试做覆盖率分析.pdf

    Java 单元测试篇:使用 Clover 为 Java 代码的 JUnit 测试做覆盖率分析 Java 单元测试是软件测试的重要组成部分,对于 Java 开发者来说,单元测试是必不可少的。今天,我们将学习使用 Clover 框架来分析 Java 代码...

    JAVA单元测试JUnit

    ### JAVA单元测试JUnit的核心知识点详解 #### 一、JUnit概览与重要性 JUnit作为一款卓越的Java单元测试框架,自问世以来便以其强大的功能和易用性深受开发者喜爱。由软件大师Erich Gamma和Kent Beck共同打造,...

    java单元测试包

    Java单元测试包主要包含了JUnit 4的不同版本,如4.7、4.8和4.11。JUnit是一个用于编写和运行Java单元测试的开源框架,它是Java开发中不可或缺的一部分,尤其是在实施持续集成和敏捷开发时。这个压缩包提供的是JUnit ...

    Java单元测试方法与技术

    本主题将深入探讨Java单元测试的方法和技术。 首先,我们需要了解Java中的主流单元测试框架JUnit。JUnit是一个开放源码的Java测试框架,它提供了编写和运行可重复测试的能力。在JUnit中,测试用例以类的形式存在,...

    Java测试与与设计——从单元测试到Web测试

    1. **JUnit框架**:是最常用的Java单元测试框架之一,提供了一套简洁的API来编写和运行测试用例。它支持数据驱动测试、测试组等高级特性。 2. **Mockito框架**:用于创建模拟对象,以隔离被测试单元与其他组件的依赖...

    JAVA单元测试接口作业.zip

    这个资源中的接口主要是为了帮助学生或者开发者更好地理解和掌握Java单元测试的概念和技术。通过这些接口,你可以学习如何编写单元测试,如何使用断言方法来检查结果,以及如何组织和运行测试用例等。此外,这个资源...

    java单元测试教程

    Java单元测试是软件开发过程中的一个重要环节,它主要用于验证代码的独立模块是否按照预期工作。在Java中,单元测试通常使用JUnit这样的框架进行,它允许开发者编写自动化测试用例,确保代码的质量和稳定性。 黑盒...

    单元测试

    1. 测试框架:为了简化单元测试的编写,通常会使用专门的测试框架。例如Java领域的JUnit,Python的unittest,C#的NUnit等。这些框架提供了一套结构化的API,帮助开发者创建、执行和组织测试用例。 2. 测试驱动开发...

    JUnit单元测试技术

    总之,JUnit作为Java单元测试的重要工具,其熟练掌握对于提升开发效率和保证软件质量至关重要。通过理解并应用其核心概念和技术,开发者可以更好地实现代码的验证和调试,从而推动项目的稳定发展。

    java 测试

    【标题】"Java 测试" 涵盖了Java编程语言中的测试技术,这包括单元测试、集成测试、系统测试等多个层次。Java测试是确保代码质量、提高软件可靠性的重要环节,它通过自动化的方式验证程序的功能是否按预期工作,以及...

    单元测试最佳实践(JAVA)

    面向对象编程是软件开发中的一项核心技能,特别是在Java这样的面向对象语言中,理解面向对象的基本概念对于进行单元测试和更广泛的软件测试至关重要。单元测试是软件测试的一个重要分支,它专注于对软件中的最小可...

    Java测试新技术TestNG和高级概念.part1

    展示了高级技术:测试部分失败、工厂、依赖关系测试、远程调用、基于集群的测试服务器群等。 介绍了在Eclipse和IDE中安装TestNG插件。 包含了大量的代码示例。 无论您使用TestNG,JUnit或其他测试框架,本书提供的...

    单元测试之道java版

    《单元测试之道Java版》这本书,正如它的标题所示,是一部关于如何进行Java单元测试的专业指南,它结合了丰富的实例和实际经验,旨在帮助开发者掌握单元测试的技巧,并在软件开发中应用这些技巧。 这本书的读者们,...

    Java程序的单元测试

    ### Java程序的单元测试 #### 一、单元测试的目的与意义 单元测试是软件开发过程中一个重要的环节,尤其是在Java程序的开发中。它主要用于验证软件中的最小可测试单元(通常是单个函数或方法)是否按预期工作。...

    基于Annotation的Java单元测试框架.pdf

    本文主要介绍了基于Annotation的Java单元测试框架,讨论了Annotation在Java EE中的应用、反射技术的使用和JUnit单元测试框架的结合,建立了一个自动化单元测试框架结构。 一、Annotation概述 Annotation是Java 5中...

    《Java测试与设计-从单元测试到自动Web测试》[PDF]

    书中介绍的技术、方法和工具能够使软件开发人员、QA技术员和IT管理员高效地协同完成软件的开发和测试自动化。 通过本书,您可以了解到包装在下一代J2EE、.NET和开源项目中的新API、协议和工具的详细清单。因为这些...

    深入学习Java单元测试(Junit+Mock+代码覆盖率)

    深入学习Java单元测试(Junit+Mock+代码覆盖率) Java单元测试是软件测试的一种,旨在验证软件的正确性和可靠性。单元测试是编写测试代码,...Java单元测试需要使用Junit框架和Mock技术,并且需要注意代码覆盖率指标。

    Java平台的单元测试技术研究.zip

    本研究主要探讨了Java平台上的单元测试技术,包括常用的单元测试框架、测试原则、最佳实践以及如何有效地集成测试到开发流程中。 一、单元测试框架 1. JUnit:JUnit是Java领域最流行的单元测试框架,它提供了编写...

Global site tag (gtag.js) - Google Analytics