关于单元测试实践?

时间:2017-01-10 12:42:58

标签: unit-testing junit

现在我有一些单元测试的问题?

商业

  

订单时应显示一些可用的优惠券列表

代码

public List<Coupon> getAvailableCouponsWhenOrder(String productCategory, int productPrice, String shopId, String userId){
    // get all available coupons -- that is not used
    List<Coupon> couponList = couponMapper.queryCoupons(userId);
    // filter coupons
    List<Coupon> availableList = new ArrayList<>();
    for (Coupon coupon : couponList) {
        // some coupon only support specific category, e.g. book, phone
        if(!checkCategory(coupon,productCategory)){
            continue;
        }
        // some coupon have use condition , e.g. full 100 then minus 50
        if(!checkPrice(coupon,productPrice)){ 
            continue;
        }
        // you cannot use other shop's coupon
        if(!checkShopCoupon(coupon,shopId)){
            continue;
        }

        availableList.add(coupon);
    }

    return availableList;
}

并且低于单元测试

@Test
public void test_exclude_coupon_which_belong_to_other_shop(){
    String productCategory = "book";
    int productPrice = 200;
    String shopId = RandomStringUtils.randomAlphanumeric(10); 
    String userId = RandomStringUtils.randomAlphanumeric(10);

    Coupon coupon = new Coupon();
    String anotherShopId = RandomStringUtils.randomAlphanumeric(10);
    coupon.setShopId(anotherShopId); // other shop's coupon
    coupon.setCategory("book"); // only use for buying book
    coupon.setFullPrice(200); // full 200 minus 50
    coupon.setPrice(50);

    when(couponMapper.queryCoupons(userId)).thenReturn(newArrayList(coupon));

    List<Coupon> availableCoupons = service.getAvailableCoupons(productCategory, productPrice, shopId, userId);
    // check whether exclude other shop's coupon
    assertEquals(0, availableCoupons.size());
}

但即使它过去了,我仍然不相信它是对的?因为可能先前的检查失败,例如checkCategory总是返回false?

那么如何知道它真的执行checkShopCoupon

2 个答案:

答案 0 :(得分:0)

远离使用模拟框架来测试您的私有方法。 (可能!)唯一需要模拟的是这个行:

List<Coupon> couponList = couponMapper.queryCoupons(userId);

这很简单:您必须确保列表couponList具有特定内容。换句话说:您创建了一整套不同的测试方法,每个测试方法都使用不同的值预先设置couponList。然后你检查返回的列表是否符合你的期望。

并且为了记录:更好地使用一个而且只断言一个人确实需要:

assertThat(service.getAvailableCoupons(productCategory, productPrice, shopId, userId), is(somePredefinedList));

(其中是Hamcrest匹配器方法)

长话短说:您可能需要使用模拟来控制对queryCoupons()的调用的返回值 - 但这是唯一需要在此处进行模拟的部分!

答案 1 :(得分:0)

在测试中需要检查两件事:逻辑正确并且逻辑被正确调用。在你的情况下,它可以是:

  • 检查逻辑的测试:CouponMapperTest#queryingCouponReturnsCouponByUserId()
  • 要检查它是否实际被调用,您需要创建一个调用getAvailableCouponsWhenOrder()的组件测试(没有模拟),然后调用couponMapper.queryCoupons(userId)

但是根据建议的方法,您可能会遇到大量的组件测试,这可能需要一段时间才能运行。为此,您需要构建一个平衡的测试金字塔(here is a thorough example),以确保您有大量的单元测试,更少的组件测试和更少的系统测试。有人建议:

  • 如果可能,请转向OOP。这意味着ObjectA包含ObjectB而不是包含IdOfObjectB。这将允许您将逻辑降低到域对象。这导致了..
  • 尽可能低地移动域逻辑(最好是它实际所属的域模型)。又名丰富的模型。然后,您将能够在不初始化应用程序的一半的情况下测试内容。那更快。

模拟框架为维护测试增加了很多负担。如果某些东西被重构,你经常需要改变一大堆测试。模拟框架的另一个问题 - 您必须单独测试事物,同时还必须确保项目作为一个整体工作。

所以我不鼓励使用模拟,除非它是你的应用程序的边界或者它是一个非常重的依赖。