编写真正的单元测试

上一篇中介绍了,何为测试驱动,为什么需要测试驱动? 现在我们来看看怎样编写单元测试。

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

这里我们改变一种方式,即我们先写功能代码,再补充单元测试,很多团队都是这样使用,虽然这样并不好,可是很多时候,我们新加入一个团队,不可能负责去做一个新的项目,都是在维护老的项目,并且当时的团队为了更快的编码而没有写单元测试(这样并不能提高效率,但是人们为了短期的利益,而这样的事情在我们生活中处处可见。)

##功能代码示例

简单了解,只需要注意 条件判断和外部依赖(调用其他类的方法),明白我们的单元测试代码需要覆盖到所有的条件判断,和隔离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 ,可以看到结果,我的这个,我已经运行成功,但是不认为这是一次就运行成功。事情都是曲折发展,在你编写单元测试的过程中(我们这种方式先编码,再补单元测试)会经常 修改单元测试代码保证单元测试代码没有问题,如果之后测试还跑不通,那么这个时候大胆的修改代码,因为你有单元测试这个强大的工具保驾护航。

这里写图片描述

测试覆盖率

测试完成之后,你还需要保证,你的单元测试代码的没有忘记哪个分支条件,此时使用 检测覆盖率工具 EclEmma
进行覆盖的检查。
安装完 EclEmma ,插件右键 coverage as —> junit test ,可以看到我们的功能代码,有了颜色标记,绿色代表 测试代码覆盖到了,红色代表没有覆盖到,黄色代表没有完全覆盖。

这里写图片描述

##总结

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

  1. 准备 junit 测试框架
  2. 创建测试类
  3. 引入 mock工具,mock外部依赖,我们这里使用 powerMock,你可以选择其他的。
  4. 检测单元测试覆盖率
  • 4
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值