logo头像
Snippet 博客主题

为什么要写单元测试

测试分类

RAD(Rap Application Development,快速应用开发)模型是软件开发过程中的一个重要模型,由于其模型构图形似字母V,所以又称软件测试的V模型,V模型大体可以划分为以下几个不同的阶段步骤:需求分析、概要设计、详细设计、软件编码、单元测试、集成测试、系统测试、验收测试。

RAD

通常研发在完成开发编码后都会进行自测,自测所包含的测试类型应当为单元测试和集成测试(联调和功能测试)。联调和功能测试大部分开发能很好的完成,因为这一部分是能够根据产品需求看得见摸得着的。往往单元测试就很容易被忽略。

什么是单元测试

如果你听说过“测试驱动开发”(TDD:Test-Driven Development),单元测试就不陌生。
所谓测试驱动开发,是指先编写接口,紧接着编写测试。编写完测试后,我们才开始真正编写实现代码。在编写实现代码的过程中,一边写,一边测,什么时候测试全部通过了,那就表示编写的实现完成了:

TDD

当然,这是一种理想情况。大部分情况是我们已经编写了实现代码,需要对已有的代码进行测试。
比如对函数abs(),我们可以编写出以下几个测试用例:

  1. 输入正数,比如1、1.2、0.99,期待返回值与输入相同;
  2. 输入负数,比如-1、-1.2、-0.99,期待返回值与输入相反;
  3. 输入0,期待返回0;
  4. 输入非数值类型,比如null,期待抛出类型错误异常。

把上面的测试用例放到一个测试模块里,就是一个完整的单元测试。
如果单元测试通过,说明我们测试的这个函数能够正常工作。如果单元测试不通过,要么函数有bug,要么测试条件输入不正确,总之,需要修复使单元测试能够通过。
单元测试通过后有什么意义呢?如果我们对abs()函数代码做了修改,只需要再跑一遍单元测试,如果通过,说明我们的修改不会对abs()函数原有的行为造成影响,如果测试不通过,说明我们的修改与原有行为不一致,要么修改代码,要么修改测试。
单元测试是开发人员自我测试的存续,一次我在做开发月供计算的时候,突然测试告知当月利率为0时月供计算异常。我分析问题后对利率为0的计算月供和本金做总贷金额/总期数和利息返回0处理,因为我更改了计算类我如何判断是否对之前逻辑有没有影响呢。这个时候我只需要将之前对该计算类的单元测试过一遍就能基本排除对之前没有影响,如下图:

Plan_test

开始编写单元测试就意味着你摆脱了祈祷式编程走上了一条代码可控的光明之路。

单元测试的特点

理想情况下,单元测试应该是相互独立、可自动化运行的。
正确的单元测试应当遵循以下特点:

资源 单元测试
网络访问
数据库访问
访问文件
访问用户界面
使用外部服务
多线程
使用sleep语句
使用系统属性设置
运行时间限制(毫秒) 60
强制时间限制(分钟) 1

如何做单元测试

说了这么多单元测试的优点,道理大家一定都懂,问题是如何做单元测试?

简单类测试-还不是手到擒来

简单类测试可以通过new一个类的方式进行测试,这样就保证后期部署时单元测试可以开启验证代码快速的验证代码的正确性(使用Spring容器服务的话启动非常慢)。

简单类测试

嵌套类测试-你得听我的

在日常开发中不可能只有一个简单类,更多的是需要借助Spring容器注入其他类进行使用,这个时候我们可以使用Mockito进行服务的注入和模拟。
我们先看一个简单的例子:
NestedBusinessService:

NestedBusinessService

NestedBusinessService下使用了OrderManager做数据获取,我们希望测试NestedBusinessService中process方法的相关逻辑,我们不想关心OrderManager是如何获取数据的(也就是不再下探OrderManager类),测试内容如下:

NestedBusinessService

这个时候我们可以使用Mockito中的mock方法将OrderManager类直接模拟出来,当碰到OrderManager类的方法时会自动返回null。有同学一定会说,这不行啊,返回null我怎么往下测试啊!
实际上你完全可以告诉被Mockito模拟的类运行到某个方式时你期望它返回什么,要达到这个目的我们可以使用Mockito的when方法,告诉它你期望执行的方法以及返回值。如下图:
NestedBusinessServiceTests:

NestedBusinessServiceTests

我们可以看到在类的最前面我们申明了orderManager的全局变量,在@Before注解的方法中我们使用Mockito的mock方法模拟了一个OrderManager对象并赋给了orderManager。
在测试代码中49行我们通过when(Mockito.when()的静态方法形式)告知了Mockito我们希望运行到orderManager对象中loadByOrderId方法时返回一个预先设置好的Order对象。
这样我们就能完成一个嵌套类的测试工作了。

多层嵌套类-我们该如何是好

刚讲了单层的嵌套类,如果碰到多层嵌套类,我们要同时测试Service层和Manager层该怎么办呢?是不是不做什么就能测试Manager层代码呢?上面我们讲了Mockito的mock方法会模拟一个对象,但是这个对象碰到方法的时候只会返回null,除非你告诉它返回什么,感觉傻傻的。我们现在介绍另外一个方法spy方法。spy方法也是模拟一个对象,但是这个对象是建立在真实类的基础上的,就好像Spring注入的类一样什么都能干。
如下图:
MultiLayerNestedBusinessServiceTests:

MultiLayerNestedBusinessServiceTests

不是说什么都能干吗?怎么报错?是的,OrderManager类是一个真真实实的类,但是它毕竟没有将代码中所有的类都加载进来,这就导致OrderManager里的嵌套类自然就GG了。我们尝试将OrderMapper类也模拟出来,还是NullPorinter,如下图:

MultiLayerNestedBusinessServiceTests

虽然我们将OrderMapper类也模拟出来了,但是orderManager对象已经是一个真实的对象了,在它初始化的时候已经加载了一个为null的OrderMapper对象,所以我们需要在orderManager对象初始化后注入一个已经模拟好的OrderMapper,如下图:

MultiLayerNestedBusinessServiceTests

此时既然orderMapper也是模拟的也就需要告诉它什么方法应返回什么,如下图:

MultiLayerNestedBusinessServiceTests

静态类-没有最强只有更强

日常代码中总会用到Util类,这些类的方法都是静态的,我们如何的完美的绕过它们呢?PowerMock是不二之选,如下图:
ImplLoadServiceTests:

ImplLoadServiceTests

按照代码逻辑计算类型传相减的话得到的结果应该是-1,但是因为我们使用PowerMock替换了getBean的返回得到了相加的结果。
PowerMock可以实现完成对private//static//final方法的Mock,就不一一列举, 反正是没有最强只有更强。

入参校验-请再爱我一次

看到标题可能会有同学说,入参有什么好校验的,入参都是我传入的啊?非也非也,当我们要请求外部接口的时候,入参肯定是要做相应适配,如何验证入参的正确性呢,请听我从头到来。
如下图:

RpcBusinessService

RpcBusinessService通过FinFeignClient请求远程服务得到用户信息并进行包装,上面的类大概就写了这些。在请求前因为用户类型和远程服务不匹配做了一次转换,我们希望在请求前进行一次请求入参校验避免转换出错。具体代码如下:

RpcBusinessServiceTests

此处通过verify方法使用ArgumentCaptor在请求前进行了入参的捕获,我们用断言判断入参是否是预期的就能够很好的检验入参的正确性了。

总结

至此单元测试相关内容就讲完了,至于如何实践还需要大家多在项目中摸索。单元测试看似是测试工作,但是对我们开发工作的延续,做的好的单元测试能够很有效的保障我们开发的功能是我们所预期的。希望大家能够重视单元测试避免祈祷式编程。

常用组件:

  1. Mockito
  2. PowerMock
  3. hamcrest

示例代码:

  1. demo-test: 单元测试实例程序

参考文档:

软件测试概念及分类整理汇总 - Findyou - 博客园
单元测试 - 廖雪峰的官方网站

#博客

微信打赏

赞赏是不耍流氓的鼓励