单元测试最佳实践示例汇总

示例一:变不确定为确定

业务

批量发放优惠券 如1张满100减50优惠券2张满200减100优惠券 先去批量创建优惠券,再批量发放优惠券

for(CouponInfo info : couponInfoList){
        Coupon coupon = new Coupon();
        coupon.setCouponCode(UUIDGenerator.getUUID32()); // 动态随机生成优惠券编码 如84678bfd7c1011e6a22b4437e6d0648e
        // ...
        couponList.add(coupon);
}
couponService.batchAdd(couponList); //批量创建优惠券

// 得到Map<CouponCode, 发放张数>
 
couponService.batchSendCouponsToUser(userId, couponCodeCountMap); //批量发放优惠券给某一用户

问题

想校验优惠券实际发放情况 如满200减100发了两张, 满100减50发放了一张, 但是优惠券编码是随机生成的, 没办法在单元测试中得知券和编码的对应关系。

解决

将生成优惠券编码的信息提取到一个独立的方法中,protected修饰,子类可以覆盖。如下所示

protected String getUUID32(CouponInfo couponInfo) {
    return UUIDGenerator.getUUID32();
}

然后单元测试中创建一个内部类继承欲测试的类并覆盖此方法

private static class MockBuyGiftsActivityService extends BuyGiftsActivityService{   
    @Override
    protected String getUUID32(CouponInfo couponInfo) {
            // couponCode同券满减信息 如100_50
            return format("%d_%d",couponInfo.getReachPrice(),couponInfo.getCouponPrice()); 
        }
    }
}

于是单元测试可以这样测

@InjectMocks
private MockBuyGiftsActivityService service; // 实际测试的是该子类
 
// 验证批量发券
ArgumentCaptor<Map<String,Integer>> couponCodeCountMapCaptor = ArgumentCaptor.forClass(Map.class);
verify(couponService).batchSendCouponsToUser(eq(userId),couponCodeCountMapCaptor.capture());
Map<String, Integer> couponCodeCountMap = couponCodeCountMapCaptor.getValue();
assertEquals(2, couponCodeCountMap.size()); // 验证共发了两种券
assertEquals(1, couponCodeCountMap.get("100_50").intValue()); // 验证满100减50发了1张
assertEquals(2, couponCodeCountMap.get("200_100").intValue()); // 验证满200减100发了2张

示例二--修改Mock方法中的对象参数值

业务

同样的业务 只不过将注入couponCode统一放到couponService中去实现了

// 组装couponList
for(CouponInfo info : couponInfoList){
        Coupon coupon = new Coupon();        
        // ...
        couponList.add(coupon);
}
couponService.batchAdd(couponList); //批量创建优惠券

// 得到Map<CouponCode, 发放张数>
 
couponService.batchSendCouponsToUser(userId, couponCodeCountMap); //批量发放优惠券给某一用户

问题

couponService.batchAdd内注入couponCode值

for(Coupon coupon : couponList){
        coupon.setCouponCode(UUIDGenerator.getUUID32()); // 动态随机生成优惠券编码 如84678bfd7c1011e6a22b4437e6d0648e
}

但单元测试实际不会进入mock方法内部

@Mock
private CouponService  couponService;

导致单元测试中couponCode均为空 没办法进行后续验证 因为所有券的编码均为NULL

解决

可以采用下面的解决方法

doAnswer(new Answer() {
    @Override
    public Void answer(InvocationOnMock invocationOnMock) throws Throwable {
        List<Coupon> couponList = invocationOnMock.getArgumentAt(0, List.class); //得到实际artisanCouponModelList参数
        couponList.get(0).setCouponId("100_50"); // 显式指定满100减50元的编码
        couponList.get(1).setCouponId("200_100"); // 显式指定满200减100的编码
        return null;
    }
}).when(couponService).batchAdd(anyList());

相当于在此修改了mock方法中的对象参数 于是后面验证券的发放情况 就方便了 如下所示

// 验证批量发券
ArgumentCaptor<Map<String,Integer>> couponCodeCountMapCaptor = ArgumentCaptor.forClass(Map.class);
verify(couponService).batchSendCouponsToUser(eq(userId),couponCodeCountMapCaptor.capture());
Map<String, Integer> couponCodeCountMap = couponCodeCountMapCaptor.getValue();
assertEquals(2, couponCodeCountMap.size()); // 验证共发了两种券
assertEquals(1, couponCodeCountMap.get("100_50").intValue()); // 验证满100减50发了1张
assertEquals(2, couponCodeCountMap.get("200_100").intValue()); // 验证满200减100发了2张

zhuguowei2
825 声望26 粉丝