`
scholers
  • 浏览: 619636 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

业务层和持久层单元测试的实践

阅读更多
  单元测试大多数时候遇到的问题难点,其实就在于怎么样很好的解决各种依赖(我这里分为外部依赖和持久层依赖吧)。根据笔者的实践,总结了如下一些实践经验,希望能够和大家共同提高。如无特殊说明:以下例子采用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层的数据。

0
3
分享到:
评论
6 楼 scholers 2011-03-09  
shaka 写道
单元测试自动化,主要是数据处理这部分比较麻烦,不知道楼主对RoR框架的单元测试是否了解,我觉得那种测试解决方案是比较舒服的,可是Java里好像没有类似的解决方案


是的,数据的处理会比较麻烦,RoR这种方式我不太了解呢。
5 楼 shaka 2011-03-09  
单元测试自动化,主要是数据处理这部分比较麻烦,不知道楼主对RoR框架的单元测试是否了解,我觉得那种测试解决方案是比较舒服的,可是Java里好像没有类似的解决方案
4 楼 scholers 2011-01-20  
finallygo 写道
楼主的文章让我学到不少,但是为什么说"代码中写hard code."不好呢?
首先用spring我觉得主要亮点就是可以使代码变得简洁,同时可以不用修改代码实现功能的替换(也就是降低了耦合),但是做测试的时候并没有这两个需求啊
如果用了spring的话,我觉得还更麻烦,尤其是测试数据多的时候,要查找也费力吧,而直接写代码里修改不是更方便吗?你说"单元测试的代码和数据相分离,这样会减少很多测试代码"指的是java的代码吧,总的工作量应该是一样的



这位兄弟说的不错,其实大部分公司或者程序员对单元测试的代码要求不高;测试数据和测试代码分离的想法来源于,测试数据的不稳定性,比如说要测试一个根据ID查询用户的函数,可能你的这个ID经常变换,总是修改代码不是很方便,可以直接来修改XML数据文件;
引申一下,如果能够做到自动化的单元测试,直接可以输入测试数据来推动测试,那就更加好了。
 
  如果只为单元测试,确实不用理会分离这个想法,凡事有好处也有坏处,象SPRING,一堆的配置文件,查找起来也麻烦的。
3 楼 finallygo 2011-01-20  
楼主的文章让我学到不少,但是为什么说"代码中写hard code."不好呢?
首先用spring我觉得主要亮点就是可以使代码变得简洁,同时可以不用修改代码实现功能的替换(也就是降低了耦合),但是做测试的时候并没有这两个需求啊
如果用了spring的话,我觉得还更麻烦,尤其是测试数据多的时候,要查找也费力吧,而直接写代码里修改不是更方便吗?你说"单元测试的代码和数据相分离,这样会减少很多测试代码"指的是java的代码吧,总的工作量应该是一样的
2 楼 scholers 2011-01-20  
smallbee 写道
DBUnit在多开发人员同数据库情况下,是不是有问题啊?



你说的这个是一个问题,多线程的情况下,还没有研究过,不知道它有没有这种机制避免这种问题;
不过它在数据库很庞大的系统做的时候,临时存储这么多数据可能会引起很大的性能问题啊。
1 楼 smallbee 2011-01-20  
DBUnit在多开发人员同数据库情况下,是不是有问题啊?

相关推荐

    iOS单元测试最佳实践

    在iOS开发中,通常会对应用的不同层级,如视图层、业务逻辑层、数据持久层等进行单元测试,确保每一部分都能正常工作,且相互之间的交互无误。此外,测试策略还应该涵盖测试的频率、测试的自动化程度以及如何有效地...

    单元测试

    在《业务层和持久层单元测试的实践.doc》中,可能会详细介绍如何针对业务逻辑层和数据访问层进行单元测试。通常,业务层的测试需要关注方法间的协作和业务规则的正确执行,而持久层的测试则要验证数据库操作的有效性...

    数据库持久层的UT测试

    总的来说,"数据库持久层的UT测试"涵盖了如何有效地对MyBatis DAO层进行单元测试,涉及到的工具和概念包括:MyBatis、DAO设计模式、JDK 1.6、Maven、JUnit、Mockito、数据库配置切换、测试数据初始化和回滚。...

    持久层设计

    ### 持久层设计——软件设计人员必读的框架级读本 #### 1. 概述 本文档旨在为软件设计人员提供一个全面、深入的理解持久层设计的指南。...通过遵循最佳实践和不断优化,可以构建出既高效又可靠的持久层。

    对单元测试的回顾1

    在给定的描述中,我们可以看到单元测试被分为两个主要部分:逻辑层单元测试和数据层单元测试,分别对应客户端工程和服务端工程。 1. **逻辑层单元测试**: - 在客户端工程的`Client`目录下的`src/test/java/...

    SSM整合实现分页以及单元测试代码

    MyBatis则是持久层框架,负责数据库操作。 1. **配置Spring**:创建`beans.xml`配置文件,定义Spring的Bean,包括数据源、SqlSessionFactoryBean、MapperScannerConfigurer等,用于扫描Mapper接口并自动创建对应的...

    用 Hibernate 和 Spring 开发持久层

    在具体的项目实践中,例如中国地质大学资讯工程学院的胡昌龙教授的研究案例中,他通过结合Hibernate和Spring构建了一个事务持久层。这个过程中,不仅实现了对象关系映射,还利用Spring的AOP框架和IOC容器,为系统...

    spring+jpa+全局异常+单元测试

    综上所述,"spring+jpa+全局异常+单元测试"这个主题涵盖了Java后端开发中的核心技术和最佳实践。通过有效地整合这些技术,我们可以构建出健壮、易于维护的业务系统,并通过严格的测试确保其质量。

    鲁棒数据库持久层设计文档

    最后,书中还涵盖了测试策略和最佳实践,如单元测试、集成测试以及如何编写易于测试的代码。这些内容有助于确保持久层的稳定性和可靠性。 总之,《鲁棒数据库持久层设计》全面覆盖了数据库持久化相关的诸多重要知识...

    面向应用的持久层设计

    在实践中,应充分考虑持久层与其他层之间的交互方式,以及如何通过抽象和封装来降低耦合度、提高内聚度。此外,随着技术的发展,还应当关注新的持久化技术和工具,以便更好地满足不断变化的需求。

    基于Java的持久层框架,零配置,零SQL操作持久层.zip

    总之,这个资料包提供了一个关于Java持久层框架的深入学习资源,尤其是对于那些希望减少配置和SQL编写的工作量的开发者来说,极具价值。通过学习和掌握这些技术,开发者可以更高效地开发和维护数据驱动的应用程序。

    三层体系结构总结:将业务规则、数据访问、合法性校验等工作放到了中间层进行处理

    在实际应用中,三层架构可能还会涉及到其他组件,如服务层(Service Layer)和持久层(Persistence Layer),它们可能是业务逻辑层和数据访问层的进一步细分。服务层可以提供更抽象的业务操作,而持久层则专注于具体...

    鲁棒的数据库持久层设计

    - **重视测试和监控**:通过充分的测试和有效的监控手段,确保持久层的稳定性和可靠性。 总之,一个鲁棒的数据库持久层不仅能够提高整个系统的性能和稳定性,还能够极大地简化开发人员的工作负担,从而提升整体项目...

    商品管理系统持久层五月份作业

    5. **单元测试**:编写单元测试来验证持久层的功能是否正确,确保添加、删除、修改和查询操作都能按预期执行。 6. **整合Spring框架**:可能需要利用Spring的IOC(Inversion of Control)和AOP(Aspect-Oriented ...

    Liferay开发持久化层和服务层演示Demo代码

    在本文中,我们将深入探讨如何使用Liferay框架进行开发,特别是关注其持久化层和服务层的构建。通过分析“Liferay开发持久化层和服务层演示Demo代码”这一主题,我们将理解Liferay Service Builder如何帮助开发者...

    spring3 + mybatis3 + junit4 可运行项目示例

    这个项目的核心是利用Spring作为应用的ioc(Inversion of Control,控制反转)和aop(Aspect Oriented Programming,面向切面编程)容器,MyBatis作为持久层框架,以及JUnit4用于进行单元测试和集成测试。...

    测试文档1

    持久层测试涉及数据库交互,包括插入、更新、删除和查询操作,确保数据的正确存储和检索。 4.1.2 业务处理层 业务处理层测试涵盖系统的核心逻辑,确保试题管理、用户管理等核心功能的正确性。 4.2 白盒测试 白盒...

    基于Hibernate和Spring的数据持久层设计与开发(软件工程课程设计).pdf

    设计基于Hibernate和Spring的数据持久层的主要目的是减少手动编写SQL语句,通过提供面向对象的方式来处理数据库操作,使开发人员能够专注于业务逻辑,而不是底层数据操作。此外,结合Spring的依赖注入(Dependency ...

Global site tag (gtag.js) - Google Analytics