1
测试桩构建(EasyMock)
构造测试桩太麻烦是项目组抱怨单元测试难做的主要原因之一,尤其是WEB应用程序开发,大量对象是由WEB容器生成,如HttpServletRequest、HttpServletResponse、ServletContext等,只有将程序布署到服务器上才能获得这些对象,这样带来的麻烦是:一方面被测对象难于孤立,输入输出难以自由控制;另一方面每次运行都要将代码布署到服务器上很浪费时间,无法脱离服务器独立运行。目前构建测试桩的首选工具是EasyMock,使用它之前需要在CLASSPATH上加上它提供的JAR包。
1) EasyMock的原理
EasyMock模拟对象的方法来自于JDK提供的对象代理机制,java.lang.reflect.Proxy类的静态方法newProxyInstance用于生成代理对象,以下是方法原型:
Object Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
其中的interfaces就是代理对象将要实现的接口,调用代理对象的方法后要执行的代码在InvocationHandler接口实现中定义。
这儿的代理对象,被EasyMock用于模拟接口对象,我们称其为Mock对象。
2) EasyMock的使用步骤
在EasyMock中,生成和使用Mock对象的步骤很简单:生成Mock对象-->录制-->回放-->验证。MockControl类是EasyMock的Façade对象,这个对象暴露了EasyMock的全部使用方法,也就是说你只需要关心这个类提供的方法即可。
u 生成Mock对象
以下代码生成了一个mock对象,类似于Java语言的new操作。但首先须获得MockControl对象,其中的Collaborator就是要模拟的接口对象。
control = MockControl.createControl(Collaborator.class);
mock = (Collaborator)control.getMock();
u 录制
获得Mock对象后默认处于录制状态,这时你可以根据你的预期指出将要调用到Mock对象的哪些方法、传给方法的参数对象是什么,进一步可以指定方法的返回值或者要求抛出一个异常。以下代码表示预期会调用到mock对象的voteForRemoval方法,传进来的参数是"Document"字符串,并且调用该方法后希望它返回值-42。
control.expectAndReturn(mock.voteForRemoval("Document"), -42);
u 回放
就是将Mock对象与被测单元关联,实施对被测单元的调用。调用到Mock对象的预期方法后就按录制时给出的方法返回值返回。以下语句表示开始回放:
control.replay();
u 验证
EasyMock能够验证的内容包括:在被测单元执行中是否按录制时列出的方法预期调用到了、传给方法的参数值是否也是预期的。只需要调用以下语句即可:
control.verify();
如果验证失败则会抛出JUnit定义的异常AssertionFailedError,表示用例执行失败。
3) “类”对象模拟
EasyMock不仅可以模拟“接口”对象,还可以模拟“类”对象,用到的主要类是MockClassControl,该类继承自MockControl,使用方法同MockControl,也就是将前面用到MockControl的地方换成MockClassControl即可。如下例:
MockControl ctrl = MockClassControl.createControl(ToMock.class);
ToMock mock = (ToMock) ctrl.getMock();
也可以只模拟部分方法,以下代码表示只模拟ToMock类的无参数方法mockedMethod:
MockControl ctrl = MockClassControl.createControl(ToMock.class, new Method[] { ToMock.class.getDeclaredMethod("mockedMethod", null) } );
4) 新版EasyMock2.0
EasyMock也有新旧版本的区别,EasyMock2.0后使用了JDK5.0的新特性泛形类(generic
class)。2.0版本的使用,大的步骤同前,主要有以下特点:
u 主要使用了JDK5.0的Generic Class
u 直接使用org.easymock.EasyMock提供的静态方法,可在文件开始静态引入:
import static org.easymock.EasyMock.*;
Ø
createMock方法用于直接生成Mock对象,省去了先获得MockControl对象的麻烦,如下例:
mock = createMock(Collaborator.class);
Ø
replay方法和verify方法可通过参数指定多个Mock对象
replay(requestObj, contextObj, dispatcherObj);
Ø
expect用于指定方法的期望返回值、调用次数、期望抛出的异常等,如下例:
expect(mock.voteForRemoval("Document")).andReturn((byte) -42);
u 验证失败抛出JDK自带的AssertionError异常
u 可在多个Mock对象间验证方法调用顺序
u 方法的参数比较规则是以方法调用形式实现的,另外还可以自定义比较器
2
数据库数据初始化与测试验证(DBUnit)
前面已经提到,业软的很多产品访问数据库的代码量占很大比例,目前各版本开发中访问数据库的技术有很多,如JDBC、EntityBean、Hibernate、Spring、iBATIS等,不管你使用哪种技术,实际上归根结蒂都是通过Java代码访问数据库,对于这些访问数据库的代码的单元测试长期以来存在以下问题:首先,如果将数据库层用桩取代,一方面构建桩的工作量巨大,另一方面即使桩能够构建完成,其实也不太容易发现代码中的BUG,因为隔离了数据库后其实代码逻辑就比较简单了,综合考虑,我们的建议是只有真实地连数据库才能真正有效地对单元做测试;但真实地连数据库又会带来另外两个问题:
u 如何确保每个用例执行前的数据库环境是可预期的?也就是数据库的初始化问题。
u 如何确保用例执行过程中正确地操作了数据库?也就是用例执行后的数据库验证。
通过在项目组的推广试用,我们发现DBUnit很好地解决了这两个问题,它的主要功能包括测试前初始化数据库数据,测试结束后验证数据库数据,另外,DBUnit提供有自定义ANT任务,结合ANT实现前述功能。要使用DBUnit也是只需要在你的CLASSPATH路径中加上它的JAR包。
1)
测试用例框架
DBUnit提供有基础类DBTestCase,该类继承自JUnit的TestCase,写测试用例的方法同JUnit,下面列出我们自己写的测试用例的基本框架,余下的任务只是增加测试方法。
public class MyDBAccessTest extends DBTestCase
{
private MyDBAccess access = null;//
这是被测类,含有要测的访问数据库的方法
static
{
//
请参见说明一
System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_DRIVER_CLASS, "oracle.jdbc.driver.OracleDriver");
System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_CONNECTION_URL, "jdbc:oracle:thin:@10.164.22.163:1521:ise");
System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_USERNAME, "tzs21911");
System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_PASSWORD, "tzs21911");
System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_SCHEMA, "TZS21911");//
必须大写
}
protected void setUp() throws Exception
{
super.setUp();
access = new MyDBAccess();
}
protected void tearDown() throws Exception
{
super.tearDown();
this.getConnection().close();
access = null;
}
@Override
protected IDataSet getDataSet() throws Exception
{
//
请参见说明二
return new FlatXmlDataSet(new FileInputStream("dataset.xml"));
}
@Override
protected DatabaseOperation getSetUpOperation() throws Exception
{
//
请参见说明三
return DatabaseOperation.CLEAN_INSERT;
}
}
u 说明一
设置要连接的数据库属性,依次包括:JDBC驱动程序类、被访问的数据库的URL、数据库登录用户名、口令、数据库SCHEMA,这些参数用于创建数据库连接。这里是通过系统属性(System
Property)的形式告诉DBUnit的,也是默认方式,另外的方式还有DataSource方式、JNDI方式,如果需要请参考javadoc文档。这里要注意的是ORACLE数据库下必须要提供有SCHEMA,且必须是字母大写,如果省略SCHEMA,则要将你的数据库连接权限配置成只能访问当前用户的SCHEMA。
u 说明二
这是必须要实现的模板方法(template method),目的是告诉DBUnit要初始化数据库的数据,数据的描述方式有多种,常用的称为FlatXml格式,其中的dataset.xml就是这种格式的文件名,下面是这个文件的例子:
<?xml version='1.0' encoding='UTF-8'?>
<dataset>
<USERS ID='1' NAME='taozs1' AGE="31"/>
<USERS ID='2' NAME='taozs2' AGE="32"/>
<USERS ID='3' NAME='taozs3' AGE="33"/>
</dataset>
<dataset>元素间的每一行表示要初始化的一条记录,其中USERS是表名,ID、NAME、AGE是表字段,=后面表示字段值。可以在一个文件中初始化多个表,但如果表之间有约束关系(如外键),要注意表的先后顺序。
该方法返回数据集(DataSet),注意数据集可以是多个表的集合
u 说明三
这是告诉DBUnit在执行用例前(测试函数运行前,在setUp方法里被调用到)要执行的数据库初始化操作(Operation),数据的来源就是前面提到的getDataSet方法返回的数据集。有多个操作类型,常用的有:
DatabaseOperation.INSERT
往数据库表里插入数据集中的数据,前提条件是数据库表里没有这些数据。
DatabaseOperation.DELETE
删除数据库里数据集包含的记录,注意数据集里不含有的记录不删除。
DatabaseOperation.DELETE_ALL
删除数据库里数据集指定的表的所有记录。
DatabaseOperation.CLEAN_INSERT
先执行DELETE_ALL操作,再执行INSERT操作,这是最常用的操作。
2)
数据库验证
在运行了被测方法后,我们需要验证该方法对数据库的操作是否正确,以下是DBUnit提供的类的两个静态方法,用于验证数据库实际结果与预期结果是否一致,如果不一至,会报用例执行不通过的错误。预期结果也是以FlatXml文件格式表示的。
public class Assertion
{
public static void assertEquals(ITable expected, ITable actual)
public static void assertEquals(IDataSet expected, IDataSet actual)
}
下面是一个验证的例子:
public void testMe() throws Exception
{
//
调用被测单元操作数据库
...
//
之后从数据库取回数据
IDataSet databaseDataSet = getConnection().createDataSet();
ITable actualTable = databaseDataSet.getTable("TABLE_NAME");
//从FlatXml格式的数据集中取回预期数据
IDataSet expectedDataSet = new FlatXmlDataSet(new File("expectedDataSet.xml"));
ITable expectedTable = expectedDataSet.getTable("TABLE_NAME");
//
验证实际数据与预期数据是否一致
Assertion.assertEquals(expectedTable, actualTable);
}
注意也可以对数据集进行验证,也就是上面的第二个方法:assertEquals(IDataSet expected, IDataSet actual)
3)
使用查询获得数据库快照
你也可以通过查询获得数据库实际数据,以下是例子:
ITable actualJoinData = getConnection().createQueryTable("RESULT_NAME",
"SELECT * FROM TABLE1, TABLE2 WHERE ...");
4)
比较时忽略某些列(字段)
对于主键字段或日期时间字段,可能是由程序动态生成,无法预期它的值,所以在比较时可以告诉DBUnit不对这些字段验证,以下是例子:
ITable filteredTable = DefaultColumnFilter.includedColumnsTable(actual,
expected.getTableMetaData().getColumns());
Assertion.assertEquals(expected, filteredTable);
也就是在你的预期FlatXml文件中只需列出你关心的字段值即可
5) DBUnit与ANT的集成
DBUnit与ANT能够集成,DBUnit提供有ANT自定义任务(TASK)扩展,这个DBUnit任务能够完成数据库初始化、数据库数据验证、数据库数据导出成XML文件等功能,这里我们以如何由当前数据库数据自动导成FlatXml文件的例子作说明,其它功能请参见javadoc。
手工编写FlatXml文件可能比较繁琐,DBUnit提供由当前数据库数据自动导出FlatXml文件的功能,之后我们可以基于这个导出的文件修改即可。导出的方式有两种,一种是通过编码实现;另一种是调用ANT任务。以下是调用ANT任务的使用说明:
u 定义dbunit任务
(1)
将DbUnit jar文件加到ant的lib目录下
(2)
Build文件的开始处添加以下行
<taskdef name="dbunit" classname="org.dbunit.ant.DbUnitTask"/>
u 调用dbunit任务
<dbunit driver="oracle.jdbc.driver.OracleDriver"
url="jdbc:oracle:thin:@10.164.22.163:1521:ise"
userid="tzs21911"
password="tzs21911"
schema="TZS21911"
classpath="C:/eclipse/lib/ojdbc14.jar">
<export dest="export.xml"/>
</dbunit>
上面的例子完成了这么几件事:通过dbunit元素属性告诉DBUnit要连接的数据库属性、通过export元素要求DBUnit导出数据库数据,属性dest是导出的文件名,属性format是导出的文件格式,默认格式是FlatXml。
6)
小结
在每个用例执行前,一般我们需要提供两个FlatXml文件,一个用于数据库初始化,一个用于用例执行后的数据库验证(预期结果)。我们在写自己的测试用例代码时注意以下几点:
u 继承自DBTestCase
u 设置数据库连接属性
u 实现getDataSet模板方法
u
getSetUpOperation中指定初始化操作
u 调用类Assertion的方法进行验证
还要指出的是我们也可以不需要继承自DBTestCase也能完成数据库的测试,详细方法就不讲了,请大家参考我的例子代码(类dbunit.NoneDBTestCaseTest)。
分享到:
相关推荐
Java平台的单元测试技术研究关注于提升应用程序的稳定性和质量,随着Java技术在中国社会的重视程度不断提升,单元测试成为保障系统质量的关键技术。研究内容主要包括单元测试技术的使用流程、测试工作的相关准则、...
总结来说,这个"java单元测试demo"是关于如何使用JUnit进行接口的功能测试,通过编写测试用例、设置断言以及可能的模拟技术,确保接口的输入和输出符合预期,从而提升代码质量和可维护性。通过运行测试并查看结果,...
Java 单元测试篇:使用 Clover 为 Java 代码的 JUnit 测试做覆盖率分析 Java 单元测试是软件测试的重要组成部分,对于 Java 开发者来说,单元测试是必不可少的。今天,我们将学习使用 Clover 框架来分析 Java 代码...
### JAVA单元测试JUnit的核心知识点详解 #### 一、JUnit概览与重要性 JUnit作为一款卓越的Java单元测试框架,自问世以来便以其强大的功能和易用性深受开发者喜爱。由软件大师Erich Gamma和Kent Beck共同打造,...
Java单元测试包主要包含了JUnit 4的不同版本,如4.7、4.8和4.11。JUnit是一个用于编写和运行Java单元测试的开源框架,它是Java开发中不可或缺的一部分,尤其是在实施持续集成和敏捷开发时。这个压缩包提供的是JUnit ...
本主题将深入探讨Java单元测试的方法和技术。 首先,我们需要了解Java中的主流单元测试框架JUnit。JUnit是一个开放源码的Java测试框架,它提供了编写和运行可重复测试的能力。在JUnit中,测试用例以类的形式存在,...
这个资源中的接口主要是为了帮助学生或者开发者更好地理解和掌握Java单元测试的概念和技术。通过这些接口,你可以学习如何编写单元测试,如何使用断言方法来检查结果,以及如何组织和运行测试用例等。此外,这个资源...
Java单元测试是软件开发过程中的一个重要环节,它主要用于验证代码的独立模块是否按照预期工作。在Java中,单元测试通常使用JUnit这样的框架进行,它允许开发者编写自动化测试用例,确保代码的质量和稳定性。 黑盒...
1. **JUnit框架**:是最常用的Java单元测试框架之一,提供了一套简洁的API来编写和运行测试用例。它支持数据驱动测试、测试组等高级特性。 2. **Mockito框架**:用于创建模拟对象,以隔离被测试单元与其他组件的依赖...
在进行单元测试时,有以下几个关键概念和技术: 1. 测试框架:为了简化单元测试的编写,通常会使用专门的测试框架。例如Java领域的JUnit,Python的unittest,C#的NUnit等。这些框架提供了一套结构化的API,帮助...
本文主要介绍了基于Annotation的Java单元测试框架,讨论了Annotation在Java EE中的应用、反射技术的使用和JUnit单元测试框架的结合,建立了一个自动化单元测试框架结构。 一、Annotation概述 Annotation是Java 5中...
面向对象编程是软件开发中的一项核心技能,特别是在Java这样的面向对象语言中,理解面向对象的基本概念对于进行单元测试和更广泛的软件测试至关重要。单元测试是软件测试的一个重要分支,它专注于对软件中的最小可...
总之,JUnit作为Java单元测试的重要工具,其熟练掌握对于提升开发效率和保证软件质量至关重要。通过理解并应用其核心概念和技术,开发者可以更好地实现代码的验证和调试,从而推动项目的稳定发展。
《单元测试之道Java版》这本书,正如它的标题所示,是一部关于如何进行Java单元测试的专业指南,它结合了丰富的实例和实际经验,旨在帮助开发者掌握单元测试的技巧,并在软件开发中应用这些技巧。 这本书的读者们,...
### Java程序的单元测试 #### 一、单元测试的目的与意义 单元测试是软件开发过程中一个重要的环节,尤其是在Java程序的开发中。它主要用于验证软件中的最小可测试单元(通常是单个函数或方法)是否按预期工作。...
【标题】"Java 测试" 涵盖了Java编程语言中的测试技术,这包括单元测试、集成测试、系统测试等多个层次。Java测试是确保代码质量、提高软件可靠性的重要环节,它通过自动化的方式验证程序的功能是否按预期工作,以及...
深入学习Java单元测试(Junit+Mock+代码覆盖率) Java单元测试是软件测试的一种,旨在验证软件的正确性和可靠性。单元测试是编写测试代码,...Java单元测试需要使用Junit框架和Mock技术,并且需要注意代码覆盖率指标。
展示了高级技术:测试部分失败、工厂、依赖关系测试、远程调用、基于集群的测试服务器群等。 介绍了在Eclipse和IDE中安装TestNG插件。 包含了大量的代码示例。 无论您使用TestNG,JUnit或其他测试框架,本书提供的...
在Java平台上进行软件开发时,单元测试是一种至关重要的质量保证技术。它允许开发者验证代码的各个独立部分(即“单元”)是否按照预期工作,从而确保整体系统的稳定性。本研究主要探讨了Java平台上的单元测试技术,...
书中介绍的技术、方法和工具能够使软件开发人员、QA技术员和IT管理员高效地协同完成软件的开发和测试自动化。 通过本书,您可以了解到包装在下一代J2EE、.NET和开源项目中的新API、协议和工具的详细清单。因为这些...