`
csd_ali
  • 浏览: 134034 次
  • 性别: 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  
不错,借鉴一下 哈哈

相关推荐

Global site tag (gtag.js) - Google Analytics