单元测试大多数时候遇到的问题难点,其实就在于怎么样很好的解决各种依赖(我这里分为外部依赖和持久层依赖吧)。根据笔者的实践,总结了如下一些实践经验,希望能够和大家共同提高。如无特殊说明:以下例子采用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层的数据。
分享到:
相关推荐
在iOS开发中,通常会对应用的不同层级,如视图层、业务逻辑层、数据持久层等进行单元测试,确保每一部分都能正常工作,且相互之间的交互无误。此外,测试策略还应该涵盖测试的频率、测试的自动化程度以及如何有效地...
在《业务层和持久层单元测试的实践.doc》中,可能会详细介绍如何针对业务逻辑层和数据访问层进行单元测试。通常,业务层的测试需要关注方法间的协作和业务规则的正确执行,而持久层的测试则要验证数据库操作的有效性...
总的来说,"数据库持久层的UT测试"涵盖了如何有效地对MyBatis DAO层进行单元测试,涉及到的工具和概念包括:MyBatis、DAO设计模式、JDK 1.6、Maven、JUnit、Mockito、数据库配置切换、测试数据初始化和回滚。...
传统的单元测试通常不涉及外部资源,但当测试的对象直接与数据库交互时(例如DAO层),就需要确保数据库处于一个已知且可预测的状态。DBunit允许开发者在测试前将数据库的状态设定为一个特定的状态,并在测试结束后...
### 持久层设计——软件设计人员必读的框架级读本 #### 1. 概述 本文档旨在为软件设计人员提供一个全面、深入的理解持久层设计的指南。...通过遵循最佳实践和不断优化,可以构建出既高效又可靠的持久层。
在给定的描述中,我们可以看到单元测试被分为两个主要部分:逻辑层单元测试和数据层单元测试,分别对应客户端工程和服务端工程。 1. **逻辑层单元测试**: - 在客户端工程的`Client`目录下的`src/test/java/...
MyBatis则是持久层框架,负责数据库操作。 1. **配置Spring**:创建`beans.xml`配置文件,定义Spring的Bean,包括数据源、SqlSessionFactoryBean、MapperScannerConfigurer等,用于扫描Mapper接口并自动创建对应的...
在具体的项目实践中,例如中国地质大学资讯工程学院的胡昌龙教授的研究案例中,他通过结合Hibernate和Spring构建了一个事务持久层。这个过程中,不仅实现了对象关系映射,还利用Spring的AOP框架和IOC容器,为系统...
综上所述,"spring+jpa+全局异常+单元测试"这个主题涵盖了Java后端开发中的核心技术和最佳实践。通过有效地整合这些技术,我们可以构建出健壮、易于维护的业务系统,并通过严格的测试确保其质量。
最后,书中还涵盖了测试策略和最佳实践,如单元测试、集成测试以及如何编写易于测试的代码。这些内容有助于确保持久层的稳定性和可靠性。 总之,《鲁棒数据库持久层设计》全面覆盖了数据库持久化相关的诸多重要知识...
在实践中,应充分考虑持久层与其他层之间的交互方式,以及如何通过抽象和封装来降低耦合度、提高内聚度。此外,随着技术的发展,还应当关注新的持久化技术和工具,以便更好地满足不断变化的需求。
总之,这个资料包提供了一个关于Java持久层框架的深入学习资源,尤其是对于那些希望减少配置和SQL编写的工作量的开发者来说,极具价值。通过学习和掌握这些技术,开发者可以更高效地开发和维护数据驱动的应用程序。
在实际应用中,三层架构可能还会涉及到其他组件,如服务层(Service Layer)和持久层(Persistence Layer),它们可能是业务逻辑层和数据访问层的进一步细分。服务层可以提供更抽象的业务操作,而持久层则专注于具体...
- **重视测试和监控**:通过充分的测试和有效的监控手段,确保持久层的稳定性和可靠性。 总之,一个鲁棒的数据库持久层不仅能够提高整个系统的性能和稳定性,还能够极大地简化开发人员的工作负担,从而提升整体项目...
5. **单元测试**:编写单元测试来验证持久层的功能是否正确,确保添加、删除、修改和查询操作都能按预期执行。 6. **整合Spring框架**:可能需要利用Spring的IOC(Inversion of Control)和AOP(Aspect-Oriented ...
在本文中,我们将深入探讨如何使用Liferay框架进行开发,特别是关注其持久化层和服务层的构建。通过分析“Liferay开发持久化层和服务层演示Demo代码”这一主题,我们将理解Liferay Service Builder如何帮助开发者...
这个项目的核心是利用Spring作为应用的ioc(Inversion of Control,控制反转)和aop(Aspect Oriented Programming,面向切面编程)容器,MyBatis作为持久层框架,以及JUnit4用于进行单元测试和集成测试。...
持久层测试涉及数据库交互,包括插入、更新、删除和查询操作,确保数据的正确存储和检索。 4.1.2 业务处理层 业务处理层测试涵盖系统的核心逻辑,确保试题管理、用户管理等核心功能的正确性。 4.2 白盒测试 白盒...
设计基于Hibernate和Spring的数据持久层的主要目的是减少手动编写SQL语句,通过提供面向对象的方式来处理数据库操作,使开发人员能够专注于业务逻辑,而不是底层数据操作。此外,结合Spring的依赖注入(Dependency ...