编写真正的单元测试

上一篇 / 下一篇  2022-07-15 10:26:22 / 个人分类:测试

为了更真实的展现单元测试的魅力,我使用目前工作中的项目一段代码,你不需要理解具体的业务,只需要了解如何如何写单元测试,以及感受单元测试的魅力就可以了。

这里我们改变一种方式,即我们先写功能代码,再补充单元测试,很多团队都是这样使用,虽然这样并不好,可是很多时候,我们新加入一个团队,不可能负责去做一个新的项目,都是在维护老的项目,并且当时的团队为了更快的编码而没有写单元测试。

功能代码示例

简单了解,只需要注意 条件判断和外部依赖(调用其他类的方法),明白我们的单元测试代码需要覆盖到所有的条件判断,和隔离MOCK 外部依赖。

 /**
     * 根据是否随机设置查询参数
     * @param params
     * @param projectType
     * @param prjId
     * @return
     */
    private  Map<String, Object> setRandParam(Map<String, Object> params,Integer projectType,Integer prjId){
     
     int allocateedAssetCountMin = (Integer)params.get("allocateedAssetCountMin");//资产池列表(前后台)最小项目已匹配资产条数
         int assetRandRoundCount = (Integer)params.get("assetRandRoundCount");//资产池(前后台)随机资产-每次查询多少条
     
         
         if (projectType != null && projectType.equals(ProjectType.LHB.getValue())) {
             prjId = LHB_PROJECTID;
         }
         boolean isRand = false;//是否使用随机债权,默认不使用
         //1.根据prjId确认是否需要使用随机债权
         if (prjId != null) {
             params.put("projectId", prjId);
            
             int investShareCount = investRepository.queryInvtShareCountByPrjId(prjId.toString());
             int allocatedAssetCount = assetRepository.queryAllocatedAssetCountByPrjId(prjId.toString());
             
             if (investShareCount == 0 || allocatedAssetCount < allocateedAssetCountMin) {
                 int randAssetListCount = assetRepository.queryAssetPoolRandListCount(params);//随机债权数据总条数
                 isRand = true;
                 Integer begin = getPoolRandListBegin(randAssetListCount, prjId, PRIME_NUMBER, assetRandRoundCount);
                 Integer end = begin + assetRandRoundCount;
                 params.put("begin", begin);
                 params.put("end", end);
             }
         }
   
         MapUtil.addValueToMap(params, "isRand", isRand);
   
    return params;
   
    }

这段代码中,有条件判断,和依赖外部(数据库)。我们需要对这个做单元测试,要保证不受外界影响(数据库的结果)所以我们需要 MOCK(模拟外部调用返回的数据)

首先为了能够 运行单元测试我们需要引入 junit(我使用过的是Java,其他语言有其他的测试框架)

因为我们需要 @test 注解,和强大的断言 Assert .

<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>

为了mock 外部的依赖,我们需要使用mock 工具,这里使用 powerMock, 引入相关的 依赖包(maven搜索,这里不再展示)。

为了能够MOCK ,我们在测试类上面使用注解 @RunWith(PowerMockRunner.class) 代表这个类需要使用mock。

在类中对 测试的类使用 @injectMocks 注解我们测试的类, @mock 注解我们外部依赖的类。

@InjectMocks 
private AssetServiceFacade assetServiceFacade;
@Mock
private InvestRepository investRepository;

测试类的命名要求 类名+Test ,测试的方法 test+方法名,编写的测试用例上面 注解@Test ,junit就知道了这个方法是测试用例(毕竟测试类中还存在一些数据准备的方法)。

现在来看看,测试用代码:

根据功能代码中的条件判断,我们需要写三个用例 (这里被测试代码由于是私有的,编写比较特殊需要使用反射来获取方法,公共方法,直接调用类就可以了。)

##用例一:条件为NULL

@Test
public void  testSetRandParamWithNull() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
//准备数据
Map<String, Object> params = Maps.newHashMap();
Integer prjId = null;
Integer projectType = null;
MapUtil.addValueToMap(params, "allocateedAssetCountMin", 500);
MapUtil.addValueToMap(params, "assetRandRoundCount", 3000);
//使用反射,调用方法
Class assetFacade = assetServiceFacade.getClass();
Method setRandParam = assetFacade.getDeclaredMethod("setRandParam",Map.class,Integer.class,Integer.class);
setRandParam.setAccessible(true);
params = (Map<String, Object>) setRandParam.invoke(assetServiceFacade, params,projectType,prjId);
Assert.assertEquals(false, params.get("isRand"));
}

用例二

@Test
public void testSetRandParamWithLHB() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
//准备数据
Map<String, Object> params = Maps.newHashMap();
Integer prjId = 3;
Integer projectType = 104040; //灵活宝
MapUtil.addValueToMap(params, "allocateedAssetCountMin", 500);
MapUtil.addValueToMap(params, "assetRandRoundCount", 3000);
//使用反射,调用方法
Class assetFacade = assetServiceFacade.getClass();
Method  setRandParam = assetFacade.getDeclaredMethod("setRandParam", Map.class,Integer.class,Integer.class);
setRandParam.setAccessible(true);
//mock
when(investRepository.queryInvtShareCountByPrjId(anyString())).thenReturn((Integer)20);
when(assetRepository.queryAllocatedAssetCountByPrjId(anyString())).thenReturn(600);
params = (Map<String, Object>) setRandParam.invoke(assetServiceFacade, params,projectType,prjId);
Assert.assertEquals(1, params.get("projectId"));
Assert.assertEquals(false, params.get("isRand"));
}

用例三

@Test
public void testSetRandParamWithRand() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
//准备数据
Map<String, Object> params = Maps.newHashMap();
Integer prjId = 3;
Integer projectType = 104040;
MapUtil.addValueToMap(params, "allocateedAssetCountMin", 500);
MapUtil.addValueToMap(params,"assetRandRoundCount", 3000);
//使用反射,调用方法
Class assetFacade = assetServiceFacade.getClass();
Method  setRandParam = assetFacade.getDeclaredMethod("setRandParam", Map.class,Integer.class,Integer.class);
setRandParam.setAccessible(true);
when(investRepository.queryInvtShareCountByPrjId(anyString())).thenReturn(20);
when(assetRepository.queryAllocatedAssetCountByPrjId(anyString())).thenReturn(20);
params = (Map<String, Object>) setRandParam.invoke(assetServiceFacade, params,projectType,prjId);
Assert.assertEquals(true, params.get("isRand"));
}

测试步骤

可以看到,上面三个的用例的大致步骤都是:

  1. 准备数据 (方法的参数)

  2. mock相关的外部依赖,直接给出我们结果,when(调用方法).thenReturn(结果)。 当调用这个方法,直接返回 期望的结果。

  3. 断言判断关键结果是否符合我们的期望。(如果不符合,则修改功能代码,前提保证测试的逻辑是没有问题的。)

运行测试

这个时候在单元测试右键 Run as -> Junit Test ,可以看到结果,我的这个,我已经运行成功,但是不认为这是一次就运行成功。事情都是曲折发展,在你编写单元测试的过程中(我们这种方式先编码,再补单元测试)会经常 修改单元测试代码保证单元测试代码没有问题,如果之后测试还跑不通,那么这个时候大胆的修改代码,因为你有单元测试这个强大的工具保驾护航。

1.png

测试覆盖率

测试完成之后,你还需要保证,你的单元测试代码的没有忘记哪个分支条件,此时使用 检测覆盖率工具 EclEmma

进行覆盖的检查。

安装完 EclEmma ,插件右键 coverage as —> junit test ,可以看到我们的功能代码,有了颜色标记,绿色代表 测试代码覆盖到了,红色代表没有覆盖到,黄色代表没有完全覆盖。

2.png总结

那么现在我们总结一下,如果编写单元测试代码:

  1. 准备 junit 测试框架

  2. 创建测试类

  3. 引入 mock工具,mock外部依赖,我们这里使用 powerMock,你可以选择其他的。

  4. 检测单元测试覆盖率


TAG:

 

评分:0

我来说两句

Open Toolbar