zlw'Blog

导航

如何写好单元测试

      代码的单元测试非常重要,是团队开发中必不可少的一环!这个懂的人,应该无需赘言了吧!!!(重要的事情说三遍)

      那如何才能写好单元测试呢?网上提到过很多优秀的项目单元测试必须满足的原则,诸如覆盖率,完整性,自动化等等。今天我想从为什么写不好单元测试,甚至是无法写出单元测试的理由入手,给出一些个人的建议与方法,希望能给大家一些启发和思考。

      为什么写不好单元测试呢?很多人给出的理由是,系统的耦合性太高了,为了测试一个接口,需要考虑太多东西了。以我们项目的实际为例,业务层的接口实现调用了事务控制层的接口,然后事务控制层的接口实现又调用了DAO层的接口,一层扣一层,真是不好写啊!!

      要写好这类代码的单元测试,普世的原则自然是解耦了。想必道理大家都懂,但是真正做起来就很麻烦了。其实,我的总结是,各层既然有明确的分工和职责,那单元测试关注的点应该也是自身的职责所在。还是以我们的项目为例,业务层的职责是面向过程的代码过程,每个接口实现的是业务流转和逻辑,业务层并不关心数据怎么存储和读取(这不应该是业务层应该关心的)。业务层的单元测试也应该服务于此,比如一个保存的接口,我们只需要测试其要保存的对象数据是否完整,校验规则是否能照顾到。有人可能问,如果这个接口有返回值,当然好验证了,那没有返回值的情况怎么验证呢?这个时候我们就需要用到mock的思想了。

      话不多说,上点代码吧。

      要测试业务层这个接口如下:

public RemoteResult<Boolean> savePushContent(final PushContentApi pushContentApi) {
        return RemoteServiceInvoker.doRunWithExHandler(new RemoteObject<Boolean>() {
            @Override
            public RemoteResult<Boolean> run() {
                RemoteResult<Boolean> result = new RemoteResult<Boolean>(true);
                pushContentApi.validateAddBean();
                if (TYPE_FASTPUSH.equals(pushContentApi.getType())) {
                    pushContentApi.setSerialNo(SERIALNOFAST_PRE + IdGenerator.genSerialNoWithDateAndRandom2());
                } else {
                    pushContentApi.setSerialNo(SERIALNO_PRE + IdGenerator.genSerialNoWithDateAndRandom2());
                }
                pushContentApi.setSendState(SEND_STATE_WAIT_CONFIRM);
                setSendInfo(pushContentApi);

                pushContentManager.savePushContent(BeanCopyUtil.createAndCopy(pushContentApi, PushContent.class));
                result.setT(true);
                return result;
            }
        });
    }

  代码里调用了pushContentManager的接口。我们mock一下这个manager,然后实现一下savePushContent一下。

@Override
    public void savePushContent(PushContent pushContent) {
        assertNotNull(pushContent);
        assertNotNull(pushContent.getSerialNo());
        assertEquals("测试内容", pushContent.getContent());
        assertEquals(Long.valueOf(-1), pushContent.getCreatorId());
        assertEquals(PushContentConstants.SEND_STATE_WAIT_CONFIRM, pushContent.getSendState());
        if (pushContent.getTaskId() == null) {
            assertEquals("13810001000,13810001001", pushContent.getTestMobiles());
            assertEquals(Long.valueOf(2), pushContent.getSendNum());
        } else if (MockDataTaskManager.DATA_TASK_ID.equals(pushContent.getTaskId())) {
            assertEquals(Long.valueOf(201), pushContent.getActivityId());
            assertEquals(Long.valueOf(100), pushContent.getSendNum());
        }
    }

  然后单元测试如下:

@Test
    public void testSavePushContent() throws Exception {
        // 测试关联数据任务的内容
        PushContentApi pushContentApi = new PushContentApi();
        pushContentApi.setContent("测试内容");
        pushContentApi.setActivityId(201l);
        pushContentApi.setCreateTime(new Date());
        pushContentApi.setCreatorId(-1l);
        pushContentApi.setCreatorName("测试用户");
        pushContentApi.setTaskId(MockDataTaskManager.DATA_TASK_ID);
        pushContentApi.setUpdateTime(new Date());
        pushContentApi.setUpdatorId(-1l);
        pushContentApi.setUpdatorName("测试用户");
        pushContentSoaService.savePushContent(pushContentApi);
        // 测试不关联数据任务的内容
        PushContentApi pushContentApi1 = new PushContentApi();
        pushContentApi1.setContent("测试内容");
        pushContentApi1.setCreateTime(new Date());
        pushContentApi1.setCreatorId(-1l);
        pushContentApi1.setCreatorName("测试用户");
        pushContentApi1.setTestMobiles("13810001000,13810001001");
        pushContentApi1.setUpdateTime(new Date());
        pushContentApi1.setUpdatorId(-1l);
        pushContentApi1.setUpdatorName("测试用户");
        pushContentSoaService.savePushContent(pushContentApi1);
    }

  验证的逻辑都放在了mock出来的manager的接口实现里,跑一下这个单元测试,就能验证正确的逻辑了。这个只是很简单的代码思路,业务层接口实现里如果有很多校验规则与逻辑的话,还需要分别测试。如果要测试真实的manager接口实现,那又再单独写他的单元测试了,但是应该是与业务层的单元测试完全解耦的。

      总结一下,单元测试要想写好,必须要灵活运用解构技巧,关注点始终放在各层的分工与职责上。而且有了这种思想,就能实现真正的测试驱动开发了(测试用例先行,你懂的)。

      

  

posted on 2017-02-24 17:21  zlw  阅读(246)  评论(0编辑  收藏  举报