`
mingj
  • 浏览: 23172 次
  • 性别: Icon_minigender_1
社区版块
存档分类
最新评论

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

阅读更多
spring2.5最大的特色就是全面使用annotation代替xml配置,包括IOC Container、springMVC和TestContext测试框架等,给我们开发带来了极大的便利。springMVC的新特性在这篇文章(注1)里面已经有了比较详尽的介绍,而对于spring的新TestContext测试框架,大家也可以从这里(注2)得到详细的例子说明,有兴趣的可以去仔细阅读,本文不再赘述。总而言之,通过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对象
3. 提供更通用的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>

与此同时,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
	}
	
	//...
}

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 测试的帮助类:
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 类就简单了许多。还是以上面的例子为例,经过这么一重构,变成如下:
import static MockTestHelper.*;

@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() {
		resetAll(this);
	}
	
	@After
	public void after() {
		verifyAll(this);
	}
	
	@Test
	public void shouldSayHello() {
		//...set expectations about foo/bar
		replay(this);
		
		helloworld.sayHello();
		//...assert verification
	}
	
	//...
}

这样看起来就好多了,以后不管在 Test 类里面添加多少个 Test 类需要的 mock 对象,我们都不需要再修改对 mock 对象的验证了,Helper类会帮我们完成所有的工作。

综上所述,使用Spring2.5里面引入的 Test Cntext 和 annotations 的确帮助我们减轻了大量的测试代码量,而且让我们的 Test 类更加POJO,更易于让人理解其职责,成为对 feature 的 specification。而 Reflection的小技巧,则能很方便的改进原来代码中不够动态的地方,进一步简化代码量和维护难度。当然我们可以看到,即使这样,代码里面还是有不少resetAll/replayAll/verifyAll的地方,作为 mock 框架带来的一些约束,我们没有办法来省略。这里推荐一种新的 mock 框架——mockito(注3),它不仅把mock、stub、spy等double的概念区分更清楚,而且让我们的 mock 测试更易写,更易读,敬请期待本博的其他文章。

References:
  • Spring 2.5:Spring MVC中的新特性:http://www.infoq.com/cn/articles/spring-2.5-ii-spring-mvc
  • 使用 Spring 2.5 TestContext 测试框架:https://www.ibm.com/developerworks/cn/java/j-lo-spring25-test/
  • mockito project homepage:http://code.google.com/p/mockito/
4
0
分享到:
评论

相关推荐

    spring 2.5依赖包

    2. **测试**:提供了更强大的测试工具和API,包括模拟对象(Mock Objects)和测试支持类。 3. **工具支持**:Spring 2.5集成了IDE工具,如Eclipse和IntelliJ IDEA,提供了更友好的开发环境。 4. **性能优化**:...

    使用 Spring 2.5 TestContext 测试框架

    在Spring框架中,TestContext模块为开发者提供了强大的测试支持,特别是在Spring 2.5版本中,这个测试框架进一步增强了测试的便利性和可扩展性。它允许我们以声明式的方式配置和管理测试环境,包括bean的初始化、...

    spring 2.5 中文pdf

    本教程将深入探讨Spring 2.5的核心概念和功能,帮助开发者充分利用这一强大的框架。 1. **依赖注入(Dependency Injection, DI)**:Spring的核心特性之一是依赖注入,它允许对象之间松耦合,提高代码可测试性和可...

    spring2.5中文开发手册

    8. Spring测试:提供了用于测试Spring组件的丰富工具和基础设施,例如,mock对象支持、测试套件配置等。 Spring框架之所以流行,是因为它将复杂的企业级应用开发抽象化,提供了许多便利的基础设施,使得开发者能够...

    Spring2.5中文帮助文档

    9. **测试框架**:Spring 2.5加强了测试支持,提供了Mock对象和测试上下文框架,使单元测试和集成测试更加容易。 10. **国际化支持**:Spring 2.5对多语言环境的支持也有所增强,允许开发者更轻松地处理不同地区的...

    spring2.5中文文档

    在测试方面,Spring 2.5引入了Mock Objects框架,使得单元测试更加容易,可以模拟复杂的协作对象,无需运行整个应用程序上下文。 综上所述,Spring 2.5中文文档详细地涵盖了这些新特性和改进,无论你是初学者还是...

    spring2.5中文操作手册

    10. **Spring测试框架**:Spring 2.5提供了对单元测试和集成测试的强大支持,包括`@Test`注解、模拟对象(Mock Objects)以及对各种测试框架(如JUnit、TestNG)的集成。 通过阅读《Spring 2.5中文操作手册》,...

    Spring2.5-中文参考手册

    6. **测试支持**:Spring Test模块在2.5中提供了更好的单元测试和集成测试支持,包括Mock对象和测试注解,使测试更加简便。 7. **国际化支持**:Spring 2.5提供了更好的国际化处理,可以通过MessageSource接口和...

    Spring 2.5 API 中文

    在Spring 2.5 API中文版中,我们可以找到关于Spring框架各个组件的详细描述,包括核心容器、数据访问/集成、Web、AOP(面向切面编程)、工具和测试等部分。这些API文档是开发者理解和使用Spring框架的关键资源。 **...

    ssh框架jar包之spring2.5

    10. **测试工具**:Spring 2.5提供了完善的测试支持,包括Mock对象、测试上下文框架等,便于编写单元测试和集成测试。 综上所述,Spring 2.5在功能和易用性方面都有显著提升,为Java开发者提供了更强大、更灵活的...

    spring2.5中文官方手册

    6. **单元测试**:Spring提供了测试支持,包括Mock对象和测试上下文框架,使得编写和执行单元测试变得更加方便。 7. **国际化支持**:Spring 2.5提供了处理多语言环境的工具,包括消息源和Locale上下文,帮助开发者...

    spring2.5中文手册(PDF)

    10. **测试支持**:Spring 2.5提供了更强大的测试工具和API,包括模拟对象(Mock Objects)和测试支持类,便于进行单元测试和集成测试。 通过阅读《Spring 2.5中文手册》,开发者不仅可以深入了解Spring框架的核心...

    Spring 2.5 jar 所有开发包及完整文档及项目开发实例

    spring.jar中包含除了 spring-mock.jar里所包含的内容外其它所有jar包的内容,因为只有在开发环境下才会用到spring-mock.jar来进行辅助测试,正式应用系统中是用不得这些类的。  除了spring.jar文件,Spring还包括...

    spring2.5.jar

    Spring 2.5提供了更强大的测试支持,包括模拟对象(Mock Objects)和测试上下文框架(Test Context Framework),使得单元测试和集成测试更加方便。 7. **Web服务的支持** 对于Web服务,Spring 2.5引入了Spring ...

    spring2.0和spring2.5 及以上版本的jar包区别 spring jar 包详解

    这个单一的jar包几乎包含了除了`spring-mock.jar`之外的所有内容,因为`spring-mock.jar`主要是在开发环境中用于辅助测试的目的,并不适合在正式的应用系统中使用。到了Spring2.5版本之后,随着功能的不断增加和技术...

    spring-framework-2.5-rc2-with-dependencies\spring-framework-2.5-rc2\spring-framework-2.5-rc2docs

    除此之外,Spring 2.5还强化了测试支持,包括模拟对象(Mock Objects)和单元测试框架的集成,使得测试过程更加便捷。这有助于开发者构建高质量、健壮的应用程序。 总之,Spring Framework 2.5 RC2是Java开发中不可...

    Spring Reference 2.5 中文

    8. **测试支持**:Spring提供了一套完整的测试框架,包括Mock对象、TestContext框架等,方便进行单元测试和集成测试。 9. **国际化(Internationalization,i18n)**:Spring通过MessageSource接口支持多语言环境,...

    spring-2.5常用项目包

    5. **Spring Test**:测试是软件开发的重要环节,Spring Test模块提供了对Spring应用的单元测试和集成测试的支持,包括Mock对象、测试上下文框架等工具。 6. **Spring JDBC**:Spring JDBC模块提供了一种抽象层,...

    pro spring 2.5

    8. **单元测试与集成测试**:Spring提供了强大的测试支持,包括Mock对象、TestContext框架等,使得开发者能够编写高效的单元测试和集成测试。 9. **Spring Web Services**:2.5版本还支持创建和消费Web服务,包括...

    string 帮助手册(spring2.5)

    通过阅读《Spring 2.5 帮助手册》中的`spring-2.5.2-reference.pdf`,你可以深入了解这些概念和功能,并学会如何在实际项目中有效利用Spring框架。手册详细解释了每个特性的用法、配置示例以及最佳实践,是学习和...

Global site tag (gtag.js) - Google Analytics