之前一直不太理解单元测试的作用,也没有写过太多的单元测试,最近做了一个抽奖项目,写单元测试的时候发现自己对这一块了解太少了,所以借着这次机会对 单元测试进行了实践。

基本问题

1. 测试什么

我理解单元测试是主要针对某个方法,如果几个方法是解决了一个问题,也可以针对几个方法,但是这样的话,其实方法写的是有问题的,要遵守单一职责的原则。

单元测试的第一个重点:测试一个方法(如果非非非常必要,可以多个)

2. 什么期望

测试某个方法如果给定确定的输入,会不会产生预期的输出。比如一个转换大小写的方法,输入a,那么预期输出就是A。

单元测试的第二个重点:明确自己的期望,知道自己要测试那些问题

3.如何处理依赖问题

一个正常的单元测试,首先就是要有Mock的数据,然后需要Mock方法中的依赖输出,最后才是看是不是方法允许是不是符合预期。我的理解是,测试的方法调用其它方法应该通过Mock的方式,只需要模拟各种返回值。

单元测试的第三个重点:Mock测试方法中调用的其他方法,注意不论是远程还是本地的依赖方法,都应该通过Mock的方式。这些依赖的方法,同样需要使用单元测试保证运行复合预期。

4.如何进行

  • 如果是私有方法,通过反射的方式测试
  • 如果是公共方法, 直接调用

5.有什么用

  • 测试代码,废话。。。
  • 代码变更后,可以跑下测试,看看是否还符合自己的预期

如果每个方法的单元测试都是符合我们预期的,那么整合起来会有问题??
如果真的会出现问题,可能要从业务的角度去多考虑,因为每个方法都符合了预期,当然也可能是单元测试没有考虑到某个问题,导致单元测试不全面

什么时候写

  1. 很多地方都说单元测试要在撸码之前写,我觉得确实这样,但是要考虑几个因素:
  • 如果要在开发之前写,那么一定要做好设计,每个方法的输入和输出都要考虑到,这还是挺有挑战性的。并且还要有最基本的方法流程。
  • 目前来说,前后端分离,会进行同步开发,所以如果一开始就写单元测试,肯定会推迟联合调试的时间,对一个快速开发的团队来说,后台压力会比较大
  1. 一边写方法,一边写单元测试
    没有尝试过这种,暂时没有感受,但是如果写完一个方法就去写测试,接着写另外的方法。这样看来,还不错,不会因为自己的方法,导致团队的开发出现阻塞(自己的方法被队友调用)。
  2. 全部撸完再写
    本次就是这种方法,开发下来最多的感受是,写单元测试的过程中发现BUG,需要修改代。。。。

未解决的问题

  1. 单元测试的方法,调用同类的方法如何Mock
  2. 私有方法的测试,如何捕获异常

遵循的原则

  1. 一定要写单元测试
  2. 架构能力强,可以之前写;一边开发,一边写,会简单一些
  3. 记住自己要测试的只是当前方法的行为,不相关的依赖方法Mock,这些依赖方法会有自己的测试方法
  4. 改完代码,跑一遍单元测试

让一个程序员明确的知道,自己的方法不会有问题

如何写

/**
     * 获取奖品
     * 期望1:如果有一般奖品获得奖品
     * 期望2:如果没有一般奖品,2.1获取默认奖品 2.2:返回没有奖品
     * 期望3:如果奖品数量为0或小于0返回Optional.Empty()
     */
    @Test
    public void getAwardTest() {
        final int defaultTotalNum = 100;
        final int zero = 0;
        //这里是Mock数据
        when(drugsGenerateService.getTotalNum())
                .thenReturn(defaultTotalNum)
                .thenReturn(defaultTotalNum)
                .thenReturn(zero);

        when(drugsGenerateService.getAllAwards())
                .thenReturn(getTestAwards())
                .thenReturn(new ArrayList<>());

        final int defaultAwardNum = 100;

        AwardDTO defaultAward = AwardDTO.builder()
                .isDefault(true)
                .desc("默认奖品")
                .num(defaultAwardNum).build();

        when(drugsGenerateService.getDefaultAward())
                .thenReturn(Optional.of(defaultAward))
                .thenReturn(Optional.empty());

        //期望1:如果有一般奖品获得奖品
        Optional<AwardDTO> awardOptional = drugsLotteryDrawService.getAward();
        Assert.assertTrue(awardOptional.isPresent());
        Assert.assertTrue(!awardOptional.get().getIsDefault());
        //期望2.1 获取默认奖品
        Optional<AwardDTO> awardOptional2 = drugsLotteryDrawService.getAward();
        Assert.assertTrue(awardOptional2.isPresent());
        Assert.assertTrue(awardOptional2.get().getIsDefault());
        //期望2.2:返回没有奖品
        Optional<AwardDTO> awardOptional3 = drugsLotteryDrawService.getAward();
        Assert.assertTrue(!awardOptional3.isPresent());
        //期望3:如果奖品数量为0或小于0返回Optional.Empty()
        Optional<AwardDTO> awardOptional4 = drugsLotteryDrawService.getAward();
        Assert.assertTrue(!awardOptional4.isPresent());
    }
  1. 一开始要明确自己的期望
  2. Mock需要的数据
    Mock工具
  3. 验证自己的期望

哪些需要写

  1. 逻辑复杂的方法
  2. 逻辑简单,但是会频繁改动(方法改动很容易影响单元测试的期望)

我的个人博客,有空来坐坐

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐