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

使用 FactoryBean结合Jmock实现动态Mock类的注入

阅读更多
Author:Willam2004
引言:
FactoryBean:我们在使用Spring过程中一般都是使用基本的的配置,在Spring配置中,还有一种特殊的FactoryBean,这种bean,可以动态的帮我们创建我们需要的bean,如: ProxyFactoryBean,通用的用于获得AOP代理的工厂bean。可以方便帮我们配置AOP的拦截类.
factorybean关键的是接口 org.springframework.beans.factory.FactoryBean,它有两个重要的方法:
            Object getObject() throws Exception;
            Class getObjectType();

getObject是要实际返回的bean对象
getObjectType是返回Bean对象对应的ObjectType.
原理可以参考:http://dengyin2000.iteye.com/blog/47443
JMock:http://www.jmock.org/
JMock是我们单元测试中经常用到的一个Mock框架.
问题:
单元测试的一个重要难点就是外部接口的依赖,为了保证单元测试的持续有效,我们对外部接口的都要进行Mock.原始的mock,是在单元测试中,直接将接口手动实现一个mock类,在单元测试中再进行注入.但这种缺点是,如果接口有变更,如新增方法(比较频繁),原有的实现类就需要重新编写.对于依赖二方库,因为更新的延迟,还会导致单元测试报错,需要重新做版本进行发布,耗时耗力.使用jmock就可以避免这个问题,因为你不需要手动再实现这个类,只需要对你关注的方法进行断言就可以了.如代码:
我们需要测试类:
public class RealService {
    private MorganService morganService;
    /**
     * @param morganService the morganService to set
     */
    public void setMorganService(MorganService morganService) {
        this.morganService = morganService;
    }
    public void doExecute(String memberId) {
        String s = morganService.findMemberById(memberId);
        System.out.println(s);
    }
}

其中MorganService服务就是我们依赖的接口类,采用jmock的测试如:
public class RealServiceTest extends TestCase {
    private RealService service;
    /**
     * Test method for {@link com.alibaba.RealService#doExecute(java.lang.String)}.
     */
    public void testDoExecute() {
        Mockery context = new Mockery();
        //Mockery是jmock的context,它负责mock对象的创建和mock检查.
        final MorganService morganService = context.mock(MorganService.class);
        context.checking(new Expectations() {
            {
                //一次调用morganService的findMemberById方法,返回值为字符串good
                oneOf(morganService).findMemberById("test120");
                will(returnValue("good"));
            }
        });
        service.setMorganService(morganService);
        service.doExecute("test120");
        context.assertIsSatisfied(); //检查mock对象有没有正确调用.
    }
}

但是目前我们的单元测试,一般是扩展Spring的单元测试进行Bean自动注入,不需要我们手动进行set方法注入,而上面Jmock的使用,需要我们将测试servcie手动调用set方法才能将Mock对象进行注入.
解决方案:
关键问题:我们不能通过配置的方式将mock对象注入到测试的服务类.FactoryBean可以帮我们动态生成我们需要的Bean进行注入.而不用关心到底是什么样的类型.
步骤一:新建MockFactoryBean

     public class MockFactoryBean implements FactoryBean {
    private String      interfaceName;
    private boolean     expectation = true;
    private IExpectaion iexpectation;
    /**
     * @return the iexpectation
     */
    public IExpectaion getIexpectation() {
        return iexpectation;
    }
    /**
     * @param iexpectation the iexpectation to set
     */
    public void setIexpectation(IExpectaion iexpectation) {
        this.iexpectation = iexpectation;
    }
    /**
     * @return the expectation
     */
    public boolean isExpectation() {
        return expectation;
    }
    /**
     * @param expectation the expectation to set
     */
    public void setExpectation(boolean expectation) {
        this.expectation = expectation;
    }
    /**
     * @return the interfaceName
     */
    public String getInterfaceName() {
        return interfaceName;
    }
    /**
     * @param interfaceName the interfaceName to set
     */
    public void setInterfaceName(String interfaceName) {
        this.interfaceName = interfaceName;
    }
    /*
     * (non-Javadoc)
     * @see org.springframework.beans.factory.FactoryBean#getObject()
     */
    @SuppressWarnings("unchecked")
    @Override
    public Object getObject() throws Exception {
        Mockery context = new Mockery();
        final Object o = context.mock(getObjectType());
        if (this.isExpectation()) {
            context.checking(new Expectations() {
                {    
                  //因为Expectations是动态的,所以我将此方法抽出来接口,方便以后的扩展
                    iexpectation.expectaion(o, this);
                }
            });
        }
        return o;
    }
    /*
     * (non-Javadoc)
     * @see org.springframework.beans.factory.FactoryBean#getObjectType()
     */
    @SuppressWarnings("unchecked")
    @Override
    public Class getObjectType() {
        // TODO Auto-generated method stub
        try {
            return Class.forName(interfaceName);
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            throw new BeanInstantiationException(this.getClass(), "Can't find the class for use the "
                                                                  + getInterfaceName());
        }
    }
    /*
     * (non-Javadoc)
     * @see org.springframework.beans.factory.FactoryBean#isSingleton()
     */
    @Override
    public boolean isSingleton() {
        return true;
    }
}
 

上面的IExpectaion是一个定义的接口,主要是用来方便自定义断言,它采用泛型的方式进行处理:
   public interface IExpectaion {
    /**
     * @param o
     * @param mockFactoryBean
     */
    void expectaion(T o, Expectations expectations);
}
 

这样我们看下我们的applicationContext的配置:
   
     
    
    
    	
    		com.alibaba.MorganService
    	
    	
          
    
 

开发人员针对每个外部接口,只需要再实现一个IExpectaion的接口类,进行相应断言就可以进行开发,而不需要再实现全部的接口方法,也避免了接口的方法变更的导致的单元测试错误.
上述方案的改进点:
1.MockFactory其实获取Object的class,可以不需要通过直接的InterfaceName来填写,直接通过iexpectation属性中的泛型参数进行获得.
其他方案
除了以上的方案,还可以通过JTester框架的Mocked注解方式进行处理.这种方式更方便,如:
@SpringApplicationContext("applicationContext.xml")
public class RealServiceJTester extends JTester {
    @Mocked
    @MockedBean
    private MorganService morganService;
    @SpringBeanByName
    private RealService   realService;
    @Test
    public void testRealService() {
        new Expectations() {
            {
                // some expectation
            }
        };
        realService.doExecute("test100");
    }
}

分享到:
评论
2 楼 Dynastqin 2014-06-26  
上述方案的改进点:
1.MockFactory其实获取Object的class,可以不需要通过直接的InterfaceName来填写,直接通过iexpectation属性中的泛型参数进行获得.

这里在java中是做不到的,因为在编译期没法确定泛型参数化类型,也就找不到对应的类字节码文件,自然就不行了
1 楼 andyjackson 2011-11-23  
不错,借鉴一下 哈哈

相关推荐

    使用FactoryBean结合Jmock实现动态Mock类的注入

    FactoryBean:我们在使用Spring过程中一般都是使用基本的的配置,在Spring配置中,还有一种特殊的FactoryBean,这种bean,可以动态的帮我们创建我们需要的bean,如: ProxyFactoryBean,通用的用于获得AOP代理的工厂bean。...

    FactoryBean代码最简实现

    本篇我们将深入探讨`FactoryBean`的最简实现及其在Spring配置文件中的使用,帮助你理解其实例化过程。 `FactoryBean`是Spring框架提供的一种扩展点,它允许我们自定义对象的创建逻辑,而不仅仅是简单的通过`new`...

    SSH笔记-通过实现FactoryBean接口来创建 Bean

    当你在Spring配置文件中声明一个Bean,并指定它的类型为`FactoryBean`的实现类时,Spring不会直接调用无参构造函数来创建Bean,而是调用`FactoryBean`的方法来获取Bean实例。 首先,我们需要了解`FactoryBean`的...

    spring的FactoryBean增强我们的目标对象.rar

    在标题“spring的FactoryBean增强我们的目标对象.rar”中,提到的核心概念就是如何利用`FactoryBean`来增强目标对象,从而在方法调用时使用代理类而不是原始对象。 `FactoryBean`是Spring中用于对象创建的一种高级...

    spring源码解析之FactoryBean相关测试代码demo

    - 创建代理对象,例如AOP代理,Spring的`AdvisedSupport`类就是一个`FactoryBean`,用于生成动态代理。 - 实现自定义的单例或多例管理。通过`FactoryBean`,我们可以控制Bean的生命周期,比如延迟初始化、按需创建...

    Spring中的FactoryBean.rar

    这个方法让Spring知道FactoryBean将要创建的对象的类,以便于类型检查和依赖注入。 使用FactoryBean的好处在于,我们可以控制对象的创建过程,包括初始化、装配和其他复杂的逻辑。例如,如果我们有一个复杂的对象...

    FactoryBean.zip

    同时,`FactoryBean`还能与Spring的AOP(面向切面编程)结合,实现更灵活的bean生命周期管理和增强。 总的来说,`FactoryBean`是Spring框架中一个强大的工具,它扩展了Spring容器的能力,使得我们可以自定义对象的...

    使用ImportBeanDefinitionRegistrar、JDK代理、FactoryBean模拟mybatis原理.zip

    当我们在配置类上使用`@Import`注解时,Spring会调用`ImportBeanDefinitionRegistrar`的`registerBeanDefinitions`方法,让我们有机会动态地向应用上下文注册bean定义。这对于实现框架级别的扩展,如自动扫描特定的...

    详解Spring中的FactoryBean

    通过实现`FactoryBean`,我们可以控制Bean的实例化过程,从而实现更灵活的依赖注入。 首先,让我们来看一下`FactoryBean`的基本使用。在上述示例中,我们创建了一个名为`Car`的简单类,它代表我们要创建的对象。...

    通过@Autowired注解注入bean的顺序,以及@bean注入.rar

    而传统的`factoryBean`则是通过实现`FactoryBean`接口来创建bean,这提供了更大的灵活性,但使用起来相对复杂。 结合`@Service`注解,它是Spring Data JPA和其他服务层组件常用的注解。`@Service`通常用于标记业务...

    简单了解Spring中BeanFactory与FactoryBean的区别

    BeanFactory的实现类有DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等,它们分别从不同的维度扩展了BeanFactory的功能。 BeanFactory的主要方法包括: * getBean(String name):根据Bean的...

    68-BeanFactory与FactoryBean1

    如果我们想获取 BeanFactory 的实例,应该使用 getBean("&beanA"),其中"&"符号表示获取 FactoryBean 的实例,而不是 getObject() 方法返回的对象。 在实际工作中,FactoryBean 是非常有用的,可以用来实例化复杂...

    factory-bean-demo.7z

    3. **接口的实现**: 如果希望为某个接口提供多种实现,可以通过`FactoryBean`动态生成不同的实现类实例。 ### 实例工程`factory-bean-demo` 在`factory-bean-demo`样例工程中,你可以看到一个具体的`FactoryBean`...

    spring中FactoryBean中的getObject()方法实例解析

    在 Spring 中,当配置文件中 <bean> 的 class 属性配置的实现类是 FactoryBean 时,通过 getBean 方法返回的不是 FactoryBean 本身,而是 FactoryBean#getObject() 方法所返回的对象。这相当于 FactoryBean#...

    com-spring-ioc-demo:源码主要是学习Spring IOC的原理,以及对Bean的注册及控制,主要运用以下类对Spring进行扩展学习:BeanPostProcessor,BeanFactoryAware,BeanNameAware,ApplicationContextAware,FactoryBean,BeanDefinitionRegistryPostProcessor,BeanFactoryPostProcessor,BeanPostProcessor,ResourceLoaderA

    com-spring-ioc-demo:源码主要是学习Spring IOC的原理,以及对Bean的注册及控制,主要运用以下类对...通过FactoryBean结合InvocationHandler关于动态代理invoke()方法的理解5.BeanNameAware 7.BeanFactoryPostPro

    spring工厂属性注入

    1. 创建实现`FactoryBean`接口的类。这个类通常会有一个或多个方法用于创建实际的业务对象。例如: ```java public class MyFactoryBean implements FactoryBean<MyBusinessObject> { @Override public ...

    spring中的FactoryBean代码示例

    今天我们来了解一下FactoryBean的实现和使用。 首先,让我们从SessionFactory说起。在使用SSH集成开发时,我们经常会在applicationContext.xml中配置Hibernate的信息。下面是一个配置SessionFactory的示例代码: `...

    初探spring aop内部实现 java

    Spring的DataSource实现和FactoryBean模式结合,可以创建动态的、灵活的数据源配置。例如,我们可以使用AbstractRoutingDataSource,这是一个可以根据上下文(如环境变量、线程绑定的属性等)动态选择数据源的...

    SpringBoot整合mybatis-plus实现多数据源的动态切换且支持分页查询.pdf

    接着,创建`DynamicDataSource`配置类,以实现数据源的动态切换: ```java @Configuration public class DynamicDataSource { @Autowired private DataSource masterDataSource; @Autowired private ...

Global site tag (gtag.js) - Google Analytics