单元测试大多数时候遇到的问题难点,其实就在于怎么样很好的解决各种依赖(我这里分为外部依赖和持久层依赖吧)。根据笔者的实践,总结了如下一些实践经验,希望能够和大家共同提高。如无特殊说明:以下例子采用junit4 + jmock1.0。
1. 如何解决外部系统的依赖?
对于业务层的单元测试,比较复杂的情况就是会有很多外部services(接口或者服务等)的依赖,其实对于我们来说最重我们需要得到或者传递出去的都是各种数据对象,那么这种情况下可以采用mock来模拟这些数据对象。这样让测试方便的通过。
场景:测试DefaultGetNumAO类的getNum方法,在真正的应用里面,调用此方法的前提是从session中取得用户信息,也就是登陆信息,但是单元测试的时候是拿不到的,如果我们直接用代码测试,那么还没有执行到getNum,单元测试就会报错,提示没有登录,其实是说这个用户信息没有取到。在线上环境我们是不会存在这个问题的。我们可以用以下代码模拟登录的用户数据(SysUser,实现的是IUser接口),这个时候我们可以用mock的方式来模拟这个外部依赖接口(或者服务)。
采用jmock的话,有两种,一种是采用接口模拟(org.jmock.Mock)的方式,一种没有实现接口的类的MOCK,是基于cglib(字节码)的方式。在这里我用的是接口模拟,我先模拟出IUser接口对象:
// 构造Mock控制器
Mock m = new Mock(IUser.class);
// 这是要测试MockObject
IUser mock = (IUser) m.proxy();
// 期待的返回值
SampleReturn sr = new SampleReturnImpl();
// 期待的参数
Parameter p = new ParameterImpl();
// 控制器,期待一次,方法sampleMethod,参数等于p(equals),将返回sr
m.expects(once()).method("getUserId").with(eq(p)).will(returnValue(sr));
其实从上面可以看出来,JMOCK或者其他Mock也好,其思想就是模拟对象,建立孤立的测试环境,上述DefaultGetNumAO类调用过多的外部系统,我们将外部系统的调用入口统一MOCK掉,MOCK出我们自己想要的数据;
我们采用下面的代码:
DefaultGetNumAO dftAo = new DefaultGetNumAO();
//这里手工输入你的mock出来的对象(外部依赖)
dftAo.setDftAo(dftAo);
//执行真正的要测试函数
Result result = dftAo. getNum ();//过程中会调用你注入的对象替代真正运行的对象或者服务
Assert.assertTrue(result.isSuccess());
采用上述的方式,可以将DefaultGetNumAO里面所有依赖的外部调用全部mock掉。
2. 如何解决对数据库的依赖?
对于DAO层来说,直接修改数据库中的物理数据,可能会带来众多冗余数据或者引起数据紊乱等情况。
场景:比如你需要测试一个insert语句,用传统的junit测试的方式,当你插入成功之后,这些你插入的测试数据实际上就成了冗余数据;更可能存在的情况,你插入这条数据之后,有些有唯一性约束的字段存在的时候,就不能再次执行插入了,这就意味着你的单元测试代码只能执行一次。
一般这种情况下采用如下方式解决:
第一, 在测试代码开始前插入临时数据,然后执行你的测试代码,待正真要测试的代码执行结束之后,在你的单元测试代码之后清理掉插入的临时数据。
如下例子:
//设置全局变量,保存数据用
private long iTestAllId = 0L;
/**
*
* setUpBeforeClass
* 初始化操作
* @throws DAOException
* @since 1.0.0
*/
@BeforeClass
public void setUpBeforeClass() throws Exception {
//先执行插入操作,得到临时数据iTestAllId
testInsertCheckList();
//进行赋值
if(iTestAllId > 0) {
checkListDO.setId(iTestAllId);
}
}
//执行结束之后,将数据清理:
/**
* 数据清理操作
*/
@AfterClass
public void tearDownAfterClass() throws Exception {
//删除数据
testDeleteCheckList();
}
第二, 在上面的基础上,手工控制事物,在测试代码执行完成之后,事物回滚,数据库恢复到刚开始执行的地方;
第三, 采用DBUnit(DBUnit的设计理念就是在测试之前,备份数据库,然后给对象数据库植入我们需要的准备数据,最后,在测试完毕后,读入备份数据库,回溯到测试前的状态,这个工具大家可以详细的在网上去查看)来模式整个数据库表,所有的操作(增删改查)都不会影响我们真实的数据库数据。
3.单元测试进阶
写代码有个原则:一般不建议在代码中写hard code.单元测试也是这样。
如果我们在代码里面写大量的hard code,既不美观,修改起来也很不方面。
那么测试数据怎么引入呢?
场景:
我们要测试CheckListDAO.java这个DAO,里面有insertCheckList方法,其参数是CheckListDO这样一个普通的对象bean.
传统的写法:
先NEW出对象
CheckListDO checkDo=new CheckListDO ();
然后赋值
//
checkDo.setId(2L);
checkDo.setType(13L);
checkDo.setName("liqf");
checkDo.setDesc("test");
然后在单元测试代码里面传入insertCheckList方法
类似这样:
int iRtn = CheckListDAO. insertCheckList (checkDo);
Assert.assertNotNull(iRtn > 0);
我们如何做?
利用spring的IOC,我们将CheckListDO交给spring去管理,所有的测试数据写在XML配置文件里面,比如可以建立一个data目录,下面建立test-dao-data.xml配置文件。
如下:
<bean id="checkListDO" class="com.liqf.CheckListDO" singleton="true">
<property name="id" value="100462" />
<property name="type" value="2" />
<property name="name" value="11" />
<property name="desc" value="11111111" />
</bean>
在测试的基类里面:
public void setUp() throws Exception {
try {
appContext = new ClassPathXmlApplicationContext(new String[] {
"data/test-dao-data.xml"
});
}
这样即使我们想修改单元测试案例,只需要修改test-dao-data.xml文件中配置的数据就可以了,是不是方便很多?
采用上述的方式,我们可以将单元测试的代码和数据相分离,这样会减少很多测试代码;
同时修改测试数据也会很方便;
4. Spring-mock简介
Spring mock直接提供了事物的自动回滚,这点是非常方便的,所以我们拿它来做DAO层的测试的时候,一点也不用关心持久层的事物处理。避免了脏数据。简化了代码
SPRING-MCOK方式,你只需要采用如下三个步骤就可以很容易实现了:
4.1首先继承AbstractTransactionalSpringContextTests(需要spring-mock.jar)
4.2重载getConfigLocations() {}方法 –这个方法里面你需要手工载入所有的配置文件,包括bean的,sql的配置
4.3写测试函数
个人以为:采用spring mock的方式将会更加容易简单的测试DAO层的数据。
分享到:
相关推荐
这些著作讨论实体、值对象、服务等DDD的主要内容,或者谈论通用语言、界定的上下文(Bounded Context)和防护层(Anti-Corruption Layer)这些的概念。 本文旨在从实践的角度探讨领域建模和设计,涉及如何着手处理...
·深入探究数据访问层的模式和最佳实践 ·为对象和数据之间的转换提供良好的解决方案 ·降低开发工作量,避免过度设计,建造更强壮的系统 第1章 当代的架构师和架构 1.1 软件架构到底是什么 1.1.1 将架构...
·深入探究数据访问层的模式和最佳实践 ·为对象和数据之间的转换提供良好的解决方案 ·降低开发工作量,避免过度设计,建造更强壮的系统 第1章 当代的架构师和架构 1.1 软件架构到底是什么 1.1.1 将...
我们通过一个简单应用,将它转换成为一个具有持久层和灵活域模型的应用。本章还介绍了一个通用Helper类:DAO模式。最后,我们还介绍了web应用程序session的管理,展示如何进行长期的商业项目和如何实施分布式应用。
Flask REST API清洁架构实践 Flask REST API的清洁架构实践。 这是一个实践项目,我用来...提供网络,持久层,缓存层等的实际实现的基础结构层。 测试 测试文件夹,尚未实现。 依存关系 flask :基本Web框架 werkzeug
MyBatis作为持久层框架,简化了数据库操作,提高了数据访问的效率。 在实现上,系统采用了JSP作为前端展示技术,结合HTML、CSS和JavaScript,构建了用户友好的界面。同时,系统还使用了Ajax进行异步数据交互,提升...
MyBatis作为数据持久层框架,实现了对数据库的灵活操作,而Spring和SpringMVC则分别负责业务逻辑的处理和前端请求的响应。 此资源的最大亮点在于其高度的可定制性和二次开发潜力。开发者可以根据实际需求,在现有...
1.2.4 持久层 13 1.2.5 关系数据库 15 1.3 使用不同类型的数据库 17 1.3.1 应用程序数据库 17 1.3.2 企业数据库 18 1.3.3 私有数据库 19 1.3.4 遗留数据库 20 1.4 iBATIS如何解决数据库的常见问题 20 1.4.1 所有权与...
无需手写SQL,使用Mybatis-generator作为持久层orm框架。 使用travis完成自动构建、测试、推送镜像实现高度自动化。 开箱即用,没有复杂的业务,是学习拓展的不二之选。 目的 随着互联网行业的发展,开发技术也在...
1.3 持久层和其他层 显示全部信息第一部分 从Hibernate和EJB 3.0开始 第1章 理解对象/关系持久化 1.1 什么是持久化 1.1.1 关系数据库 1.1.2 理解SQL 1.1.3 在Java中使用SQL 1.1.4 面向对象...
MyBatis作为数据持久层,简化了数据库操作,提高了开发效率。 前端Vue.js框架的运用,使得页面渲染更加流畅,用户体验更佳。Vue的数据驱动和组件化开发思想,不仅提高了开发效率,也便于后期的维护与扩展。 此项目...
- 无需手写SQL,使用Mybatis-generator作为持久层orm框架。 - 使用travis完成自动构建、测试、推送镜像实现高度自动化。 - 开箱即用,没有复杂的业务,是学习拓展的不二之选。 ## 目的 > 随着互联网行业的发展,...
在数据库处理方面,不需要在数据层借助存储过程及数据库服务器端函数封装过多的业务逻辑,因此数据库系统采用相对精巧的MySQL[6]。 该在线博客系统服务器端如果需要布置到其他主机上,则该主机必备条件如下: 1. ...
为了方便程序调试,针对基本模块的单元测试也往往和编码结合在一起进行。单元测试也以“详细设计说明书”为依据,用于检验每个基本模块在功能、算法与数据结构上是否符合设计要求。 4.系统集成测试 所谓系统集成也...
src 源代码⽬录 -main 存放实现类的源代码 --bean model类 --controller 控制器类 --dao 持久层--service 实现业务功能服务 --util ⼯具类 -test 测试类 三、系统运⾏环境 三、系统运⾏环境 操作系统:...
13.8 SSD、系统操作和层之间的联系 13.9 示例:NextGen的逻辑架构和包图 13.10 示例:Monopoly逻辑架构 13.11 参考资源 第14章 迈向对象设计 14.1 敏捷建模和轻量级UML图形 14.2 UML CASE工具 14.3 编码前...
13.8 SSD、系统操作和层之间的联系 13.9 示例:NextGen的逻辑架构和包图 13.10 示例:Monopoly逻辑架构 13.11 参考资源 第14章 迈向对象设计 14.1 敏捷建模和轻量级UML图形 14.2 UML CASE工具 14.3 编码前...
13.8 SSD、系统操作和层之间的联系 13.9 示例:NextGen的逻辑架构和包图 13.10 示例:Monopoly逻辑架构 13.11 参考资源 第14章 迈向对象设计 14.1 敏捷建模和轻量级UML图形 14.2 UML CASE工具 14.3 编码前...
13.8 SSD、系统操作和层之间的联系 13.9 示例:NextGen的逻辑架构和包图 13.10 示例:Monopoly逻辑架构 13.11 参考资源 第14章 迈向对象设计 14.1 敏捷建模和轻量级UML图形 14.2 UML CASE工具 14.3 编码前...
13.8 SSD、系统操作和层之间的联系 13.9 示例:NextGen的逻辑架构和包图 13.10 示例:Monopoly逻辑架构 13.11 参考资源 第14章 迈向对象设计 14.1 敏捷建模和轻量级UML图形 14.2 UML CASE工具 14.3 编码前...