论坛首页 综合技术论坛

Service层的单元测试:与其Mock别人,不如Mock自己

浏览 3172 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2011-11-28  
此为抛砖贴,希望能引出更好的见解。

你会怎么单元测试这样一个service层的类?
public class Service {

    
    private BizOne bizOne;
    
    private BizTwo bizTwo;

    public boolean doBiz(Object param) {

        Object resultOne = bizOne.getIt(param);
        if (resultOne != null) {
            return true;
        }
        return bizTwo.getIt(param) == null;

    }
    ...
}


不管怎么测,对bizOne和bizTwo的调用都得mock掉,否则你的测试类可能会直接访问数据库或者依赖容器。所以你可能会这样写测试代码:

        // do mocking
        BizOne bizOne = expectBizOneNotReturnNull(param);
        BizTwo bizTwo = expectBizTwoReturnNull(param);

        // inject mocked objects
        service.setBizOne(bizOne);
        service.setBizTwo(bizTwo);

        assertTrue(service.doBiz(param));




看起来不错,但有两个小问题:
  1. Mock的bizOne, bizTwo对象需要再通过setter方法注入到service对象中。一种测试场景就得写一次setter注入,多种测试场景就得写多次了,这会比较繁琐,而且可能会让测试代码失去重点。

  2. expectBizOneNotReturnNull, expectBizTwoReturnNull 跟业务逻辑无法直接对应。被测类背后的真实契约是“符合bizOne逻辑”且“符合bizTwo逻辑 => 返回true”,而不是“A为空,B不为空之类的”


何不改成这样:

        //do mocking
        expectBizOneValid(service, param);
        expectBizTwoValid(service, param);

        assertTrue(service.doBiz(param));


这里就不用再手工用setter拼装了,而且,契约感也很强:expectBizOneValid + expectBizTwoValid => assertTrue

你还顺便获得了一个好处:你会意识到,被测代码本身也应该增强契约感,应该这样改写:
public class Service {

    private BizOne bizOne;
    private BizTwo bizTwo;

    public boolean doBiz(Object param) {
        //用isBizOneValid()替代bizOne.getIt(param)!=null, 代码能更直接地反应业务逻辑,这也是《重构》这本书里极力推荐的作法
        if (isBizOneValid(param)) {
            return true;
        }
        return isBizTwoValid(param);

    }

    //这里要用包可见性方法,因为测试类里要mock它
    boolean isBizTwoValid(Object param) {
        return bizTwo.getIt(param) == null;
    }

    boolean isBizOneValid(Object param) {
        Object resultOne = bizOne.getIt(param);
        return resultOne != null;
    }
 ...



现在Service本身的实现也很像契约了。当然,你也可以看出副作用: 只mock Service类的部分方法,这需要mock框架支持; 另外,isBizTwoValid()这种私有契约要设成包可见性以方便被同一个包下的测试类mock(否则就得javaagent + 反射,很麻烦)


好处和坏处都有,看你的口味了。最后总结一下 --关于“Mock Service这个类自己的方法,而不是Mock BizOne, BizTwo”这种做法

1.好处:
  a.Mock别人就意味着要做一次组装,有点烦
  b.Mock可以强迫被测类的内部接口更具契约感,测试类更具契约感

2.坏处
  a.Mock框架需支持Partial Mock (推荐jmockit)
  b.被Mock的内部方法要么设成包可见性(损失封装性),要么设成private并通过方式名反射来Mock(损失方法名的重构安全性)
   发表时间:2011-12-16  
Mock也可以mock class的,不只是mock接口,像easyMock class;实现复杂的业务逻辑时,推荐使用“自继承”+ easyMock 模式
0 请登录后投票
论坛首页 综合技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics