注册本站  论坛  繁體中文

慧民电脑芯片级维修-电脑技巧
手机 | MP3 | MP4 | 显卡 | 主板 | 显示器 | 光存储 | 笔记本 | 网络设备 | 移动存储 | 数码相机
键鼠 | CPU | 音箱 | GPS | 电视 | 服务器 | 投影机 | 机箱电源 | 品牌电脑 | 办公打印 |
| 网站首页 | Cisco | Windows | Linux | Java | Dotnet | Oracle | 网页设计 | 平面设计 | 安全 | 软件应用 | 电脑维修 | 办公维修 |
您现在的位置: 电脑技巧 >> Java >> 开源技术 >> Spring >> Java正文

利用spring2.5和Reflection简化测试中的mock

文章来源:中国IT实验室整理 作者:佚名 更新时间:2008-10-6 12:42:12 【 】 【加入收藏
    spring2.5最大的特色就是全面使用annotation代替xml配置,包括IOC Container、springMVC和 TestContext测试框架等,给我们开发带来了极大的便利。springMVC的新特性在这篇文章里面已经有了比较详尽的介绍,而对于spring的新TestContext测试框架,大家也可以从这里得到详细的例子说明,有兴趣的可以去仔细阅读,本文不再赘述。总而言之,通过spring2.5提供的 annotation,我们可以让我们的类——包括controller,Test等职责特殊的类——更 POJO 化,更易于测试,也提高了 TestCase的开发效率。 

    在开发过程中,我们通常需要mock特定的对象来测试预期行为,或者使用stub对象来提高单元测试效率。最常见的例子就是在多层webapp中,在controller类的测试方法里mock或 stub底层dao类的方法,从而减轻单元测试时数据库操作的开销,加快单元测试速率。至于Reflection,已不是java的新概念了,各样框架基本上都有使用Reflection来增强Runtime的动态性。而java5里Reflection效率的提升和annotation的引入,更是极大地提高java语言的动态性,让开发人员得到更多Runtime的灵活性。本文将演示如何使用spring2.5和Reflection简化测试中的 mock,使用的JUnit框架是JUnit4.4,mock框架是Easymock2.4。

    让我们先看看最原始的使用mock对象的测试(假设基于jdk5进行开发,使用了包括static import,varargs等新特性):
import static org.easymock.EasyMock.*;

public void HelloworldTest extends AbstractSingleSpringContextTests {
    private Foo foo = createMock(Foo.class);
    private Bar bar = createMock(Bar.class);
    private Helloworld helloworld;
    
    @Before
    public void before() {
        reset(foo, bar);
        helloworld = new Helloworld(foo, bar);
    }
    
    @After
    public void after() {
        verify(foo, bar);
    }
    
    @Test
    public void shouldSayHello() {
        //set expectations about foo/bar
        replay(foo, bar);
        
        helloworld.sayHello();
        //assert verification
    }
    
    //
}

    可以看到,因为使用了 Spring 老版本的 TestContext,上面的代码至少有两个方面是需要加强的:
    1. 需要大量的 mock 对象创建操作,与真正的 Test Case 无关的繁琐代码,而且还引入了对Spring Context Test 类的继承依赖
    2. 针对不同的 Test 类,因为用到不同的 mock 对象,每次都需要显式去指明 reset/replay/verify 用到的 mock 对象

    针对上面的两个问题,我们有相应的解决方案来改进:
    1. 使用spring来替我们创建mock对象,由spring IOC Container在runtime注入需要的mock对象 
    2. 提供更通用的rest/replay/verify机制来验证mock对象,而不是每个 Test 类都需要单独处理 

    1. 每个mock对象都需要手工创建么?答案当然是否定的,我们有FactoryBean。通过在配置文件中指定bean的定义,让spring来替我们创建mock对象。如下是针对Foo类的定义:
<bean id="mockFoo" class="org.easymock.EasyMock" factory-method="createMock">
    <constructor-arg index="0" value="Foo"/>
</bean>

    < /constructor-arg>与此同时,Spring TestContext框架提供了 @ContextConfiguration annotation 允许开发人员手工指定 Spring 配置文件所在的位置。这样,开发过程中,如果开发人员遵循比较好的配置文件组织结构,可以维护一套只用于测试的对象关系配置,里面只维护测试用到的 mock 对象,以及测试中用到的对 mock 对象有依赖关系的对象。在产品代码中则使用另一套配置文件,配置真实的业务对象。 

    JUnit4.4 之后,Test 类上可以通过 @RunWith 注解指定测试用例的 TestRunner ,Spring TestContext框架提供了扩展于 org.junit.internal.runners.JUnit4ClassRunner 的 SpringJUnit4ClassRunner,它负责总装 Spring TestContext 测试框架并将其统一到 JUnit 4.4 框架中。这样,你可以把 Test 类上的关于 Spring Test 类的继承关系去掉,并且使用 JUnit4 之后引入的 annotation 去掉其他任何 JUnit3.8 需要的约定和方法继承,让 Test 类更加 POJO。 

    Test 类也是“纯正” 的 java 对象,自然也可以通过 Spring 来管理依赖关系:在 Test 类的成员变量上加上 @Autowired 声明,使用 SpringJUnit4ClassRunner 运行 Test Case。Spring 会很聪明地帮助我们摆平 Test 依赖的对象,然后再运行已经“合法”的 Test Case,只要你在用于测试的配置文件里面定义了完整的依赖关系,一如其他正常对象。
<bean id="Helloword" class="Helloworld" autowire="byType"/>
    这样,经过上面三点变化,例子代码变成了这样:

import static org.easymock.EasyMock.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("test-context.xml")
public void HelloworldTest {
    @Autowired
    private Foo foo;
    
    @Autowired
    private Bar bar;
    
    @Autowired
    private Helloworld helloworld;
    
    @Before
    public void before() {
        reset(foo, bar);
    }
    
    @After
    public void after() {
        verify(foo, bar);
    }
    
    @Test
    public void shouldSayHello() {
        //set expectations about foo/bar
        replay(foo, bar);
        
        helloworld.sayHello();
        //assert verification
    }
    
    //
}


    < bean id="Helloword" class="Helloworld" autowire="byType">2. 现在看上去是不是好多了?嗯,对象间的依赖关系和mock对象的创建都由 Spring 来替我们维护,再也不用费心了。不过,reset/verify 是不是还是看上去那么舒服?我们观察一下,通常为了简化对 mock 对象的验证,我们对 Test 类中使用到的 mock 对象都是一起reset/replay /verify,要是能有resetAll()/replayAll()/verifyAll()方法就好了,也省得不同的 Test 类写一大串对不同的 Mock 对象验证的方法。OK,这时候我们就要借助 Reflection 来完成这项任务了:通过 Reflection 得到 Test 类中所有加上 @Autowired 声明的成员变量,验证它们是不是由代理或者字节码增强,从而得到该 Test 类的所有由 Spring 创建的 mock 对象,进行 reset/replay/verify.

    根据这个思路,我们引入这样一个 mock 测试的Helper类:

import static org.easymock.EasyMock.*;

final class MockTestHelper {

    public static void resetAll(Object testObject) {
        reset(getDeclaredMockedFields(testObject));
    }

    public static void verifyAll(Object testObject) {
        verify(getDeclaredMockedFields(testObject));
    }

    public static void replayAll(Object testObject) {
        replay(getDeclaredMockedFields(testObject));
    }

    private static Object[] getDeclaredMockedFields(Object testObject) {
        Field[] declaredFields = testObject.getClass().getDeclaredFields();
        List declaredMockedFields = new ArrayList();
        for (Field field : declaredFields) {
            if (field.isAnnotationPresent(Autowired.class)) {
                boolean isAccessible = field.isAccessible();
                try {
                    field.setAccessible(true);
                    Object value = field.get(testObject);
                    if (isClassProxy(value.getClass())) {
                        declaredMockedFields.add(value);
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                finally {
                    field.setAccessible(isAccessible);
                }
            }
        }
        return declaredMockedFields.toArray();
    }

    private static boolean isClassProxy(Class clazz) {
        String className = clazz.getName();
        return className.contains("$Proxy") || className.contains("$$EnhancerByCGLIB$$");
    }

}


    好了,有了这么一个 Helper 类,写 mock 对象的Test 类就简单了许多。还是以上面的例子为例,经过这么一重构,变成如下:

    这样看起来就好多了,以后不管在 Test 类里面添加多少个 Test 类需要的 mock 对象,我们都不需要再修改对 mock 对象的验证了,Helper类会自动< bean id="Helloword" class="Helloworld" autowire="byType">帮我们完成所有的工作。& lt;br />综上所述,使用Spring2.5里面引入的 Test Cntext 和 annotations 的确帮助我们减轻了大量的测试代码量,而且让我们的 Test 类更加POJO,更易于让人理解其职责,成为对 feature 的 specification.而 Reflection的小技巧,则能很方便的改进原来代码中不够动态的地方,进一步简化代码量和维护难度。当然我们可以看到,即使这样,代码里面还是有不少resetAll/replayAll/verifyAll的地方,作为 mock 框架带来的一些约束,我们没有办法来省略。这里推荐一种新的 mock 框架—— mockito,是有我的外国同事开发的,它不仅把mock、stub、spy等double的概念区分更清楚,而且让我们的 mock 测试更易写,更易读。

  • 上一篇Java:

  • 下一篇Java:
  • 最 新 热 门
     利用Ruby简化你的Java测试(进阶篇)
     IIS+tomcat整合,并且通过域名访问网站
     Eclipse客户端程序中多线程的使用
     实现向eclipse控制台输入消息
     浅谈Acegi配置-Spring-Java
     反思Spring:由Ruby on Rails想到的
     利用spring2.5和Reflection简化测试中的moc…
     struts2.0表单提交带参数问题
     JSF与Struts的比较 超易懂!
     Struts从零开始六、Struts的模块使用实例
    最 新 推 荐
     浅谈Acegi配置-Spring-Java
     反思Spring:由Ruby on Rails想到的
     利用spring2.5和Reflection简化测试中的moc…
     spring 核心包介绍
     Spring MVC中的新特性
     Spring AOP 用annotation 来实现
     Spring操作Hibernate更方便
     Spring Security 2资源-角色查找源码研究及…
     Spring3.0新特征-Restful support MVC
     Spring Autowire(自动装载)的一次实践
    相 关 文 章

    利用Ruby简化你的Java测试(进阶篇)
    浅谈Acegi配置-Spring-Java
    反思Spring:由Ruby on Rails想到的
    详细了解JSP中九个隐含对象
    利用Ruby简化你的Java测试
    spring 核心包介绍
    Spring MVC中的新特性
    Spring AOP 用annotation 来实现
    Spring操作Hibernate更方便
    Spring Security 2资源-角色查找源码研究及…

    | 设为首页 | 加入收藏 | 联系站长 | 友情链接 | 版权申明 | 网站公告

     

    Copyright 2006-2008 pcjx.com All Rights Reserved
    电脑技巧 版权所有 粤ICP备06059145号 地图
    门市地址:广东省佛山市南海区黄岐黄海路133号
    本网站所有内容未经许可不得转载或做其他使用