Java测试那点事

最近公司在整顿大家的单元测试,会定期出coverage报告。我个人还是挺支持这事的,一方面能培养大家的开发习惯,也能提升代码质量(如果不考核就更好了,哈哈)。现在公司使用的是Spring框架,大家现在所说的单元测试其实更应该算是集成测试,因为很多人会测试交易报文的正确性、整个交易的执行正确性、数据库操作的正确性。

在测试过程中发现很多我以前没在意的问题,也让我对代码测试有了更深的理解。下面简要介绍一下:

1. Spring Bean中使用static变量:

使用Spring的初衷是使用它的IOC特性,能够装配出想要的bean,这些类只需要是普通的POJO对象就可以了,但是有些人却使用static类型来保存数据,更可气的是不支持多次加载(会判断如果static变量中有值时就报错),这样就把Spring的优点消失殆尽了。其实这样只是为了可以直接使用静态函数访问这个类。

这样做的缺点是显而易见的,在连续执行多个测试案例(suite)时,如果Spring需要重新加载(见我上一篇文章),则会报错,因为不支持多次加载。解决方法目前想到的只能使用ant通过fork出一个新的jvm的方式来进行测试。或者使用Powermock工具通过单独的classloader加载,但这样会导致Spring context不能复用、找不到某些类如log4j等、代码覆盖度报告无显示等很多其他问题。

2. 使用final、static等不可测试的方法:

普通的Mock方式一般是针对接口,或是通过继承来修改一个类的方法,如果遇到这些方法,想要进行单元测试,一般的Mock工具都不好使。但如果为了测试而修改代码风险又很大,好在现在发现一个好用的工具Jmockit,它可以解决上述不可测代码的问题。它是通过java.lang.instrument包结合ASM字节码工具来实现的,具体可以参考此博文。我原来是使用Powermock来做的,它是通过自己的Classloader在类加载时进行修改来实现的,相比之下Jmockit要更直接一些,功能也更加强大。

[注]: Jmockit官网的教程是最新的,如果要看历史版本的文档可以在这里下载,里面包括源码和文档。

3.测试覆盖度报告工具使用

目前有很多测试覆盖度工具,包括coberturajacocoJmockit code coverage。其中eclispe的代码覆盖度工具EclEmma使用的就是jacoco。每个代码覆盖度工具提供的覆盖度报告是不同的,包括行覆盖度、分支覆盖度等等,具体大家可以参考它们的文档。下面说下使用不同工具应该关注的点:

  1. 是否支持maven、ant工具,是否可以支持多个工程报告合并
  2. 你使用的mock方法和这些代码覆盖度工具是否兼容
  3. 能提供的报告内容是否满足你的需要,你是否认可它的指标
  4. 生成覆盖度报告的样式你是否满意

测试覆盖度工具、Mock工具建议大家了解一下它们的原理,可以在网上搜以下。这样大家在使用时可以更得心应手。

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《ITechLib》

Spring Test的一些小技巧

目前项目在使用Spring Test进行单元测试(其实算是集成测试了),有两个小知识想和大家分享下。

1.对于同时存在注解、xml配置的bean会以xml中的配置为准

例如spring配置文件如下:

    <!-- application.xml -->
    <context:component-scan base-package="com.itechlib.com.service" />
    
    <!-- 在测试目录下定义stub类 -->
    <bean id="userDao" class="com.itechlib.com.test.stub.UserDaoStubServiceImpl" />

Java代码如下:

@component("userService")
class UserServiceImpl implements IUserService {

@Resource(name="userDao")
private IUserDao userDao;

  public void doUpdate(){
    //...
    userDao.update();
    //...
  }
}

这样在测试时UserServiceImpl调用的就是stub包下定义的userDao的模拟类了。

2.同时运行多个测试类的时候,可以有不同的ContextConfiguration配置

例如使用Junit TestSuite来一次执行多个测试类,这时如果不同测试类的ContextConfiguration配置不一样会怎么样呢?其实这些测试类都能够正确运行,因为spring会针对不同的configuration创建不同的ApplicationContext,而且这些ApplicationContext都会进行缓存,只会加载一次。

下面这些key不同就会生成不同的ApplicationContext,具体见spring reference 文档 Context caching

  • locations (from @ContextConfiguration)
  • classes (from @ContextConfiguration)
  • contextInitializerClasses (from @ContextConfiguration)
  • contextLoader (from @ContextConfiguration)
  • activeProfiles (from @ActiveProfiles)
  • resourceBasePath (from @WebAppConfiguration)

如果测试中破坏了ApplicationContext,可以对这个测试类声明@DirtiesContext

例如下面的写法会生成两个独立的ApplicationContext。

@ContextConfiguration(loader = SpringBeanTestContextLoader.class, locations = {
    "classpath:test-resources/application/application.xml"})
class UserServiceTests extends AbstractJunit4SpringContextTests{
    @Test
    public void testUpdate(){
    //...    
    }
}

@ContextConfiguration(loader = SpringBeanTestContextLoader.class, locations = {
    "classpath:test-resources/application/application-custom.xml"})
class OrderServiceTests extends AbstractJunit4SpringContextTests{
    @Test
    public void testAdd(){
    //...    
    }
}

了解这些你可以更好的规划你的测试配置,更好地测试各种场景和分支。

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《ITechLib》