单元测试之mockito、powermock、jmokit初探及选择

 

  • 背景

我要测试某个方法,但是这个方法有很多的依赖,如HSF服务依赖、数据库依赖等,本来写好的时候测试用例跑的好好的,结果某一天就呵呵了,要么是数据库中的测试数据被改了,要么是依赖的HSF服务关闭了(大哥,我在部署我的应用呢,对不住了),当然,有的时候心血来潮,要体验下测试驱动开发,哥们你作为一只老猿/媛,piapiapia,两下子搞完了,你的小伙伴只能眼睁睁的看着完全跟不上你的节奏啊,他提供不了你需要的数据,咋办?为了解决这些痛点,我们需要对这些外部调用都进行mock,好了,下面扯扯我的mock选择之路吧。

  • Mockito篇

很不开心的表示以前写的测试用例应该都属于集成测试,初次接触mock框架,两眼一抹黑,看到小伙伴在用着mockito,依葫芦画瓢,kaka写上几个再说。

被测试方法:
public ExtTicketInfoModel buildExtTicketInfoModel(QueryTicketInfoResponse response,
                                                      TicketInfoRequest ticketInfoRequest,
                                                      TerminalInfo terminalInfo) {
    ******
    List<VoucherEntity> voucherEntities = voucherRepository.queryVoucherByCodesAndCinema(null,
            fetchCode, cinemaId, null, partnerCode);
    ******
}

测试用例:

//指定mock执行框架
@RunWith(MockitoJUnitRunner.class)
public class BuildExtTicketInfoModelTest extends AbstractUnitBase {
    
    // 被测试的类
    @InjectMocks
    private ChenxingServiceHelper chenxingServiceHelper = new ChenxingServiceHelper();

    // 需要mock的外部依赖
    @Mock
    private VoucherRepository     voucherRepository;

    @Test
    public void testBuildExtTicketInfoModel() throws Exception {
        // build request
        String responseXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><QueryTicketInfoResult><ResultCode>0</ResultCode><Message>成功</Message>******</QueryTicketInfoResult>";
        Serializer serializer = new Persister();
        QueryTicketInfoResponse response = serializer.read(QueryTicketInfoResponse.class, responseXml, false);
        TicketInfoRequest ticketInfoRequest = new TicketInfoRequest();
        ticketInfoRequest.setExtCinemaNo("12345678");
        ticketInfoRequest.setFetchCode("12345678");
        TerminalInfo terminalInfo = new TerminalInfo();
        terminalInfo.setRouteCode("chenxing");

        // mock response object
        List<VoucherEntity> vouchers = new ArrayList<VoucherEntity>();
        VoucherEntity voucher = new VoucherEntity();
        voucher.setId(1L);
        voucher.setTbOrderId(111L);
        voucher.setGmtCreate(new Date());
        vouchers.add(voucher);

        // stub,俗称打桩,指的是当被测试的方法中调用了该外部依赖时,该外部依赖将被mock 
        Mockito.when(
            voucherRepository.queryVoucherByCodesAndCinema(anyString(), anyString(), anyLong(), anyString(),
                anyString())).thenReturn(vouchers);

        ExtTicketInfoModel result = chenxingServiceHelper.buildExtTicketInfoModel(response,
            ticketInfoRequest, terminalInfo);
        println(result);
        Assert.assertNotNull(result);
        Assert.assertTrue(result.getExtTicketModels().size() > 0);
        Assert.assertEquals(TerminalConstants.TAOBAO_SELLER_NAME, result.getExtVoucherModel().getSellerName());
    }
}

针对通常的依赖于接口的调用mockito用着很顺利,然而,当我写着写着,发现我有个待测试方法居然调用了个外部静态方法,找来找去也没发现mockito有相关的mock能力,继续挖挖挖,矮油,powermock这货不错,居然可以mock静态方法,私有方法好像很强大的样子嘛。

  • powermock篇

这里引用一篇文章,学习时所参考的,很不错,写的很详细,我就不再缀诉了
(这里没贴地址,内网地址)

然而当我写完之后发现,我又有了个新的需求,我想要知道我的代码覆盖率如何了,总不能每写几个,就上amon看看我的代码覆盖率吧,这样效率真心有点low,继续挖,铛铛铛,还真有个niubility的东西,jmockit,不但拥有powermock的所有功能,而且还能统计代码的覆盖率。

  • jmockit篇
maven依赖:
<!-- jmockit -->
<dependency>
    <groupId>org.jmockit</groupId>
    <artifactId>jmockit</artifactId>
    <version>1.23</version>
    <scope>test</scope>
</dependency>
<!-- 如果需要统计覆盖率,则需要加上如下包 -->
<dependency>
    <groupId>org.jmockit</groupId>
    <artifactId>jmockit-coverage</artifactId>
    <version>1.23</version>
    <scope>test</scope>
</dependency>
 上码:
被测试方法:

public ResultGeneralModel<?> queryVoucherInfo(TerminalInfo terminalInfo,
                                                  TicketInfoRequest ticketInfoRequest) {
        ******
        try {
            // 查询票据接口
            String responseXml = ClientUtil.queryTicketInfo(requestXML, chenxingPartnerDetail.getUrl());
            if (StringUtil.isBlank(responseXml)) {
                return null;
            }
            Serializer serializer = new Persister();
            response = serializer.read(QueryTicketInfoResponse.class, responseXml, false);

        } catch (Exception e) {
            throw new TFTerminalRuntimeException(CommonErrorCodeEnum.SYSTEM_ERROR,
                "Fetch chenxing VoucherInfo,ticketInfoRequest:" + ticketInfoRequest, e);
        }
        
        ******
        
        ResultGeneralModel<ExtTicketInfoModel> queryResult = chenxingServiceHelper
            .convertQueryResult(response, extTicketInfoModel);
        return queryResult;
    }
  测试用例:

// 指定Jmockit,这样在启动的时候会自动进行初始化的操作
@RunWith(JMockit.class)
public class QueryVoucherInfoTest extends AbstractUnitBase {

    // 被测试的类
    @Tested
    private ChenxingPartnerServiceImpl service;

    @Injectable
    private PartnerComponent           partnerComponent;
    // 外部接口依赖
    @Injectable
    private ChenxingServiceHelper      chenxingServiceHelper;
    
    // 依赖的静态方法
    @Mocked
    private ClientUtil                 clientUtil;
    
    private static final String        partnerCode = "chenxing";

    @SuppressWarnings("static-access")
    @Test
    public void testQueryVoucherInfo() throws Exception {
        TicketInfoRequest ticketInfoRequest = new TicketInfoRequest();
        ticketInfoRequest.setExtCinemaNo("12345678");
        ticketInfoRequest.setFetchCode("12345678");
        TerminalInfo terminalInfo = new TerminalInfo();
        terminalInfo.setRouteCode(partnerCode);
        
        // 录制预期值
        new Expectations() {
            {
                // mock 静态方法调用
                clientUtil.queryTicketInfo(anyString, anyString);
                result = mockQueryTicketInfoResponseXml();
                // mock普通接口依赖
                chenxingServiceHelper.convertQueryResult((QueryTicketInfoResponse)any, (ExtTicketInfoModel)any);
                result = mockConvertQueryResult();
            }
            
        };

        ResultGeneralModel<?> result = service.queryVoucherInfo(terminalInfo, ticketInfoRequest);
        println("Chenxing QueryVoucherInfo-正常", result);
        Assert.assertNotNull(result);
        Assert.assertEquals(result.getReturnCode(), "1");
    }
}

// 很多时候我们需要了解异常情况下程序执行会如何,而异常情况又很难得到,这时候也可以使用mock,录制片段如下:
clientUtil.queryTicketInfo(anyString, anyString);
result = new SocketTimeoutException("调用远程服务超时");
直接new出来即可,是不是很简洁。
2016-10-08补充:
发现jmockit mock之后,被测试的类打了断点,但是却进入不了断点的,前两天实在纠结,就特意查了一下,有了如下解决方案:先在test方法中打好断点,启动测试用例,再到被测试方法中打断点,这样被测试类的断点就会起效了。
原理:因为jmockit的mock是通过修改字节码来达到目的的,所以在启动前打的断点的类并不是实际应用执行的类,只有待启动后,类加载器加载后去打断点才能真正打在被修改字节码后的类上(有点绕),参考:http://stackoverflow.com/questions/28594487/debugging-java-code-tested-with-spock-and-jmockit

覆盖率统计:mvn test或直接在IDE中执行完test项目之后,会自动生成覆盖率统计文件,目录下有index.html,直接查看就可。

JMockit: Coverage report written to D:\work\workSpace\projectName\test\target\coverage-report

 

至此在初步完了3个mock框架之后,最终以选择jmockit而告终,只因其强大和简洁。
 

  • 留在最后的话

在使用过程中碰上一种场景,不知道这样的代码如何进行测试覆盖:

public class AService {
    
    private BService bService;
    
    public void getUserProp() {
        Model model = new Model();
        bService.changeModel(model);
        if (model.getUserId() != null && model.getUserId().longValue() == 888888) {
            System.out.println("我是888888");
        }
    }
}


public class BService {

    public void changeModel(Model model) {
        Random random = new Random();
        Long userId = random.nextLong();
        model.setUserId(userId);
    }
}
以上这种传递对象,对象被更改,然后再之后又会使用对象中的某些值来实现某些逻辑的又如何mock呢,求了解的同学支高招。

转载于:https://my.oschina.net/EvanDev/blog/1622614

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值