目录
[2、Mockito 中文文档地址](#2、Mockito 中文文档地址)
6.核心业务、核心应用、核心模块的增量代码,确保单元测试通过。
2.编写单元测试代码时遵守BCDE原则,以保证被测试模块的交付质量。
1.Mock可以用来解除外部服务依赖,从而保证了测试用例的独立性。
2.Mock可以减少全链路测试数据准备,从而提高了编写测试用例的速度。
3.Mock可以模拟一些非正常的流程,从而保证了测试用例的代码覆盖率。
4.Mock可以不用加载项目环境配置,从而保证了测试用例的执行速度。
前言
为了保证代码质量,在写完代码后,写单测是很有必要的。当然,在大部分情况下,我们可能不会写单测,而是直接把应用部署起来,直接自测,然后再联调。估计很大一部分人,都是用这种方式开发。当然,我之前也是按这个方式来开发,单测覆盖率纯粹是为了满足公司的指标要求,大部分流于形式。
一、为什么写单元测试
说到单元测试,就不得不提起另一个词,TDD(Test-Driven Development)测试驱动开发:在开发功能代码之前,先编写单元测试用例代码,测试代码确定需要编写什么产品代码
测试驱动开发虽然饱受争议,不过有这种方法论的推出并有不少的同行在践行,起码能够说明测试的重要性。
1、当我们想测试部分代码逻辑是否正常的时候,我们可能会直接psvm
来构造数据进而调试。那如果有一种东西能把我们psvm
统一放到某个地方呢?
2、当我们在一个系统里边修改了很多代码时,又不确定改动是否影响在核心逻辑时。那如果有一种东西能在编译的时候,顺便自动跑一遍逻辑做回归呢?无论是重构还是正式提测前,都提高了自己写代码的信心。
3、当我们很容易一不小心时就把代码写成一坨屎,那如果有一种东西能让我们在编码的时候就注重自己的代码设计呢?
4、当我们这个季度什么都没干,但是系统没发生过故障,那如果有一种东西能让我们在KPI上添上浓墨的一笔呢?
5、....欢迎补充
没错,这东西就是单元测试。
写单测好处
慢慢的,我感受到写单测带来的几个好处:
1、提升效率
启动一个应用,几分钟,找bug,修bug,再重启应用,这个过程不断的重复,应用重启太浪费时间。
而单测不需要重启整个应用,只对几个service做测试,效率高很多。
2、场景覆盖全
单测可以对代码运行中的各种情况进行模拟,并对最终的返回结果断言,这是自己自测很难模拟的。而且,这些单测,是沉淀的资产。下次修改代码,可以重跑以前单测,发现问题,避免踩坑。
单测怎么写
我们很容易对单测产生误解,所以这里我先把2个概念说明一下:
1、集成测试
测试过程中,会启动整个Spring容器,调用DB 或者 依赖的外部接口等。只不过访问的环境是测试环境。这个过程最大程度还原生产环境过程,但是耗时长。
2、单元测试
不启动整个应用,只对单个接口/类进行测试。不调用DB 、外部接口,依赖的服务都Mock掉,只测试代码逻辑。这个过程,测试用例耗时短。
我们说的单测,是指第2种。
单测过程分2步,第一步:Mock外部依赖,第二步:断言
Mock框架
1、Mockito单元测试
java
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.3.3</version>
<scope>test</scope>
</dependency>
2、Mockito 中文文档地址
Mockito库能够Mock对象、验证结果以及打桩(stubbing)。
GitHub - hehonghui/mockito-doc-zh: Mockito框架中文文档
二、强制要求
1.好的单元测试必须遵守AIR原则。
-
A:Automatic(自动化)
-
I:Independent(独立性)
-
R:Repeatable(可重复)
单元测试在线上运行时,感觉像空气(AIR)一样感觉不到,但在测试质量的保障上,却是非常关键的。好的单元测试宏观上来说,具有自动化、独立性、可重复执行的特点。
2.单元测试应该是全自动执行的,并且非交互式的。
测试用例通常是被定期执行的 ,执行过程必须完全自动化才有意义。输出结果需要人工检查的测试,不是一个好的单元测试。
单元测试中不准使用System.out 来进行人肉认证,必须使用assert来验证。
3.保持单元测试的独立性。
为了保证单元测试稳定可靠且便于维护,单元测试用例之间绝不能互相调用 ,也不能依赖执行的先后次序。
4.单元测试是可以重复执行的,不能受到外界环境的影响。
单元测试通常会被放到持续集成中 ,每次有代码check in 时单元测试都会被执行。
如果单测对外部环境(网络、服务、中间件等)有依赖,容易导致持续继承机制的不可用。
正例:为了不受外界环境影响,要求设计代码时就把 SUT 的依赖改成注入,在测试时用 spring 这样的 DI框架注入一个本地(内存)实现或者 Mock 实现。
被测系统
被测系统(System under test, SUT)表示正在被测试的系统,目的是测试系统能否正确操作。
根据测试类型的不同, SUT 指代的内容也不同, 例如 SUT 可以是一个类甚至是一整个系统。
5.对于单元测试,要保证测试粒度足够小。
有助于精确定位问题 ,单测粒度至多是类级别,一般是方法级别。
6.核心业务、核心应用、核心模块的增量代码,确保单元测试通过。
说明:新增代码及时补充单元测试,如果新增代码影响了原有单元测试,请及时修正。
7.单元测试代码目录
必须写在如下工程目录:src/test/java,不允许写在业务代码目录下。
说明:源码编译时会跳过此目录,而单元测试框架默认是扫描此目录。
三、推荐要求
1.单元测试的基本目标
语句覆盖率达到70%,核心模块的语句覆盖率和分支覆盖率都要达到100%。
说明:在DAO层,Manager层和可重用度高的Service中都应该进行单元测试。
分支覆盖率 :5个分支,那么对应的应该有10条语句(一个分支有两条语句,ture和false),如果你执行了其中的5条,那么覆盖率就是50%。
2.编写单元测试代码时遵守BCDE原则,以保证被测试模块的交付质量。
-
B:Border,边界值测试 ,包括循环边界、特殊取值、特殊时间点、数据顺序等。
-
C:Correct,正确的输入,并得到预期的结果。
-
D:Design,与设计文档相结合,来编写单元测试。
-
E:Error,强制错误信息输入,并得到预期的结果。
3.对于数据库的相关查询、更新和删除等操作
不能假设数据库里的数据是存在的,或者直接操作数据库将数据插进去,请使用程序插入或者导入数据的方式来准备数据。
4.和数据库相关的单元测试
可以设定自动回滚机制 ,不给数据库造成脏数据,或者对单元测试产生的数据有明确的前后缀标识。
正例:在企业智能事业部的内部单元测试中,使用 ENTERPRISE_INTELLIGENCE UNIT_TEST 的前缀来标识单元测试相关代码。
5.对于不可测的代码
在适当的时机做必要的重构,使代码变得可测,避免为了达到测试要求而书写不规范测试代码。
6.在设计评审阶段
开发人员需要和测试人员一起确定单元测试范围,单元测试最好覆盖所有测试用例。
7.单元测试作为一种质量保障手段
在项目提测前完成单元测试,不建议项目发布后补充单元测试用例。
四、参考要求
1.为了更方便地进行单元测试,业务代码应该避免一下情况
-
构造方法中做的事情过多
-
存在过多的全局变量和静态方法
-
存在过多的外部依赖
-
存在过多的条件语句
说明:多层条件语句建议使用卫语句、策略模式、状态模式等方式重构。
如果条件语句极其复杂,就应该将条件语句拆解开,然后逐个检查,并在条件为真时立刻从函数中返回,这样的单独检查通常被称之为"卫语句"(guard clauses)
摘自《重构---改善既有代码的设计》
卫语句的效果就是将原来需要仔细阅读代码、细心整理逻辑的条件判断整理成一眼能看透的逻辑关系,效果就像以下:
java
if(obj != null){
doSomething();
}
转换成卫语句以后的代码如下:
java
if(obj == null){
return;
}
doSomething();
2.不要对单元测试存在如下误解
-
那是测试同学干的事情。单元测试也是和开发同学强相关的。
-
单元测试代码是多余的。系统的整体功能和各单元部件的测试真长与否是强相关的。
-
单元测试代码不需要维护。一年半载后单元测试几乎都会处于废弃朱状态。
-
单元测试与线上故障没有辩证关系。好的单元测试能够最大限度地规避线上故障。
五、单元测试与集成测试的区别
在实际工作中,不少同学用集成测试代替了单元测试,或者认为集成测试就是单元测试。
单元测试与集成测试的区别:
1.测试对象不同
单元测试对象是实现了具体功能的程序单元 ,集成测试对象是概要设计规划中的模块及模块间的组合。
2.测试方法不同
单元测试中的主要方法是基于代码的白盒测试 ,集成测试中主要使用基于功能的黑盒测试。
3.测试时间不同
集成测试要晚于单元测试。
4.测试内容不同
单元测试主要是模块内程序的逻辑、功能、参数传递、变量引用、出错处理及需求和设计中具体要求方面的测试 ;而集成测试主要验证各个接口、接口之间的数据传递关系,及模块组合后能否达到预期效果。
六、为什么要使用Mock做测试
比如你现在想要测试一个方法是否是正常的,但是这个方法中有很多调用数据库的代码 ,那么我们就可以在每个调用数据库的地方打桩 ,模拟一下访问完数据库之后的返回值,这样我们就可以在测试的时候避免访问数据库了,可以非常高效地完成我们的单元测试,已达到验证我们写的方法到底对不对的目的。
1.Mock可以用来解除外部服务依赖,从而保证了测试用例的独立性。
现在的互联网软件系统,通常采用了分布式部署的微服务,为了单元测试某一服务而准备其它服务,存在极大的依耐性和不可行性。
2.Mock可以减少全链路测试数据准备,从而提高了编写测试用例的速度。
传统的集成测试,需要准备全链路的测试数据,可能某些环节并不是你所熟悉的。最后,耗费了大量的时间和经历,并不一定得到你想要的结果。现在的单元测试,只需要模拟上游的输入数据,并验证给下游的输出数据,编写测试用例并进行测试的速度可以提高很多倍。
3.Mock可以模拟一些非正常的流程,从而保证了测试用例的代码覆盖率。
根据单元测试的BCDE原则,需要进行边界值测试(Border)和强制错误信息输入(Error),这样有助于覆盖整个代码逻辑。在实际系统中,很难去构造这些边界值,也能难去触发这些错误信息。而Mock从根本上解决了这个问题:想要什么样的边界值,只需要进行Mock;想要什么样的错误信息,也只需要进行Mock。
4.Mock可以不用加载项目环境配置,从而保证了测试用例的执行速度。
在进行集成测试时,我们需要加载项目的所有环境配置,启动项目依赖的所有服务接口。往往执行一个测试用例,需要几分钟乃至几十分钟。采用Mock实现的测试用例,不用加载项目环境配置,也不依赖其它服务接口,执行速度往往在几秒之内,大大地提高了单元测试的执行速度。