单元测试(一)--相关概念

前言

最近项目需求提测bug数一直维持在一个较高的数字,leader让我们去注意和提高提测质量。
确实,单元测试都知道重要性可真要去写往往自己很多时候要不就是是不愿意要不就比较随意测测,然后写个大致的程序能调通接口后就万事大吉扔给测试了。因为说实话,单测是真的需要你非常的理解整个你所开发的流程并且需要去准备这些所需的数据,想到这一点再加上害怕失败的心理,就很容易产生退缩和应付了事的感觉。
不过呢,我觉得这也是提升自己的一个时机,往往难受的时刻意味着你可以突破自己的瓶颈,所以在此简单的总结和拓展一下公司对单元测试的一个讲解。在此分享给大家,希望能够共同进步。

一、什么是单元测试

​ 单元测试(unit testing),简称单测,是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,**单元就是人为规定的最小的被测功能模块。**单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。

​ 单元测试都是以自动化的方式执行,所以在大量回归测试的场景下更能带来较高的收益。

二、为什么要写单测

  1. 单元测试证明你的代码真的可用。
  2. 你得到一个低层级的回归测试套件,当你做了修改或重构,可以通过跑单元测试,迅速回归验证你的代码是否又问题。
  3. 尽早发现bug,并及时修复,如:你写好的代码可能几周之后才能联调,通过单元测试你可以不必等到联调的时间,更及时的发现代码中的问题。
  4. 更容易定位bug, 因为单元测试是隔离的。
  5. 单元测试造就更好的设计,如果你觉得单元测试很难写,那么你的代码大概率设计不够好(如低内聚,高耦合),你需要重新设计它。
  6. 你能在没有破坏设计的情况下改进它,单元测试代码是一种调用端对被调用端的约束,设计好的单元测试代码通常是不易变的,在这种约束下,可以保证你的修改或重构不偏离原本的设计。
  7. 单元测试是示例代码的一部分,你可以通过查看单元测试代码,学习怎么样使用(调用)“业务”代码。
  8. 单元测试前期需要耗费一定工作量,但从长远来看,收益远大于成本

三、单测的衡量指标

首先是能够保证软件的“正确性”,所谓正确,主要从以下几个方面理解:
流程符合预期,即按照设计的步骤运行,并在关键的步骤执行正确的功能,包含正确的参数;

执行效果符合预期,即通过功能执行后,能够产生符合设计的结果,这种结果可以是直接的,比如方法的返回值;也可以是间接的,比如方法改变了实例中可被观察到的部分;

异常保护符合预期,功能执行过程会遭遇超越设计边界的场景,保护自身不因“越界”而失效;

质量属性符合预期,某些功能具有质量属性,如响应时间等。

单测覆盖率
以JaCoCo单测覆盖率测算框架为例有一下覆盖率指标:
JaCoCo应该为基于Java VM的环境中的代码覆盖率分析提供标准技术。重点是提供一个轻量级,灵活且文档齐全的库,以与各种构建和开发工具集成。
JaCoCo官方文档:https://www.eclemma.org/jacoco/trunk/doc/index.html

  • ​ 指令(C0
    Coverage):JaCoCo计数的最小单元是单一的Java字节码指令。指令覆盖率提供了关于字节码执行数量、未执行数量的信息。
  • ​ 分支(C1
    Coverage):对所有的if和switch语句计算分支覆盖率。统计在方法中分支执行数量、未执行数量的信息。但要注意,异常处理不在此计算范围内。
  • ​ 圈复杂度(Cyclomatic
    Complexity):对非抽象方法计算圈复杂度,并汇总类、包和组的(圈)复杂度。这个值可以做为单元测试用例是否完全覆盖的参考。
  • ​ 行(Lines):一行可能包含一条或多条指令,如果至少有一条指令被执行了,那么该行就算作是被执行了。
  • ​ 方法(Methods):每个非抽象方法至少包含一条指令。如果至少有一条指令被执行了,那么该方法就算作是被执行了。
  • ​ 类(Classes):如果类中至少有一个方法被执行了,那么该类就算作是被执行了。
    关于jacoco更详细的可以参考https://blog.csdn.net/skh2015java/article/details/121775806

​ 其中最常用的是行覆盖率和分支覆盖率,其他的指标可供参考。

  • 从持续集成的角度来讲,可分为全量覆盖率和增量覆盖率

​ 全量覆盖率:用来描述单测整体的覆盖情况。

​ 增量覆盖率:用来描(本次)述新提交的代码中,单测的覆盖情况,一些框架(如JaCoCo)并不支持增量覆盖率,需要自己扩展开发。

在实际开发过程中,我们关注的较多的是:全量行覆盖率,全量分支覆盖率,增量行覆盖率,增量分支覆盖率。

单测覆盖率通常是衡量单测是否达标的一个重要标准,但如果为了追求覆盖率而忽略了‘正确性’(甚至为了达到要求的覆盖率,故意写一些奇特的单元测试代码以图蒙混过关),则是与我们的期望背道而驰的

四、Java怎么写单测

1.写单元测试的一些原则
一般要求单元测试符合FIRST原则和AIR原则

  • FIRST原则
  • F-FAST(快速原则)
    单元测试应该是可以快速运行的,在各种测试方法中,单元测试的运行速度是最快的,通常应该在几分钟内运行完毕;
  • I-Independent(独立原则)
    单元测试应该是可以独立运行的,单元测试用例互相无强依赖,无对外部资源的强依赖
  • R-Repeatable(可重复原则)
    单元测试应该可以稳定重复的运行,并且每次运行的结果都是相同的
  • S-Self Validating(自我验证原则)
    单元测试应该是用例自动进行验证的,不能依赖人工验证
  • T-Timely(及时原则)
    单元测试必须及时的进行编写,更新和维护,以保证用例可以随着业务代码的变化动态的保障质量
  • AIR原则
    ①A-Automatic(自动化原则)
    单元测试应该是自动运行,自动校验,自动给出结果
    ②I-Independent(独立原则)
    单元测试应该是独立运行,互相之间无依赖,对外部资源无依赖,多次运行之间无依赖;
    ③R-Repeatable(可重复原则)
    单元测试是可重复运行的,每次的结果都稳定可靠
    以上原则,要求我们在写单元测试时,不依赖外部资源(通过打桩或Mock处理外部依赖),不持久化调用结果,尽量不依赖Spring上下文(如不在单测中使用Autowired等注解),单测中使用断言判断结果,而非打印结果。

2.对单元测试的一些要求

  • 每次只对一个对象进行UT测试(unit-test one object at a time)。这样能使你尽快发现问题,而不被各个对象之间的复杂关系所迷惑。
  • 给测试方法起个好名字(choose meaningful test method names)。应该是用形如testXXXYYY(),这样的格式来命名你的测试方法。XXX应该是你测试
    的方法名,YYY应该是你测试的状态。当然如果你只有一种状态需要测试可以直接命名为testXXX()。
  • 明确写出出错原因(explain the failure reason in assert calls)。在使用assertTrue,assertFalse,assertNotNull,assertNull方法时,应该将可能的错误的描述
    字符串,以第一个参数传入相应的方法。这样你可以迅速的找出出错原因。
  • 一个UT测试方法只应该测试一种情况(one unit test equals one testMethod)。一个方法中的多次测试,只会混乱你的测试目的。
  • 测试任何可能的错误(test anything that could possibly fail)。你的测试代码不是为了证明你是对的,而是为了证明你没有错。因此对测试的范围要全面,比如边界值、正常值、错误值;对代码可能出现的问题要全面预测。
  • 让你的测试帮助改善你的代码(let the test improve the
    code)。测试代码永远是我们代码的第一个用户,所以不仅让他帮组我们发现Bug,还要帮组我们改善我们的设计,就是有名的测试驱动开发(Test-Driven
    Development,TDD)。
  • 一样的包,不同的位置(same package, separate directories)。测试的代码和被测试的代码应该放到不同的文件夹中, 这样可以让两份代码使用一样的包结构,但是放在不同的目录下。

3.单元测试的一些概念

  • 被测系统(System under test,SUT)

表示正在被测试的系统,目的是测试系统是否能正确操作。

  • 第三方依赖组件(depended-on component,简称DOC)

驱动代码(Driver)
指调用被测函数的代码,在单元测试过程中,驱动模块通常包括调用被测函数钱的数据准备、调用被测函数以及验证相关结果三个步骤。

测试替代(Test Double)
细分为以下几种:

哑对象 (Dummy Object)
Dummy是在测试中仅发挥占位填充符的作用,在测试中不会被使用,也不参与测试行为或状态的验证。常见的场景是作为被测函数的某个参数占位符,以减少参数的构造成本。

冒充对象(Fake Object)
Fake 是模拟被测系统所依赖的那个组件,它是生产环境下这个被依赖组件的功能实现的简化版本。Fake对象是用于测试的,但它既不是测试中的控制点(control point),也不是观测点(observation point)。

一个常见的使用场景就是利用 Fake 来保证在测试环境下支付永远返回成功结果,前提是被测对象并不是支付系统。

桩对象(Stub Object)
Stub提供在测试过程中对请求调用的屏蔽式应答,通常对该测试程序之外的任何内容都无响应。即:Stub只是返回一个规定的值,而不会去涉及到系统的任何改变。它通常是测试中的控制点(control point)。

比较常见的场景就是系统希望去查询某一类的信息,而Stub可以总是返回一个固定值,比如发送邮件的功能,Stub可以总是返回邮件发送成功的标识1,但是你并不知道你到底发送了邮件给谁或者发送了几封邮件。

间谍对象(Spy Object)
Spy可以看做是一类Stub,但是,它会记录它在被调用后的一些信息。例如,它被用于模拟email 服务,并记录用它发送了多少邮件。

交互模拟对象(Mock Object)
Mock 通常会被作为观察点,用于验证被测系统(SUT)在执行时的间接输出(即观测点)。通常,Mock对象还会发挥Stub的作用,因为如果测试尚未失败,它必须将值返回到SUT,但其重点是验证间接输出。因此,一个Mock对象不仅仅是一个Stub和断言,它的使用方式有着根本的不同。所以,它既可以是观察点( Observation Point ),也可以发挥 控制点( Control Point )的作用。

Mock和Stub有一定的重合性,比较大的区别是 Mock 专注于观察点,而 Stub 专注于 控制点。或者从另一个角度上面来说,Mock会验证行为的变更,而Stub只是状态的一个变化而已。

4.单元测试用例设计
https://www.jianshu.com/p/38e889e0a8f0
5. JAVA单元测试框架及代码示例
Java常用的测试框架或平台:Junit, TestNg, Dbunit, Mockito, PowerMock, Spring test framework, Easy mock在下面我们将会结合一些示例来说明怎么样其中的一些框架如何使用(主要介绍Junit, Mockito),并对需要注意的地方加以说明。

Junit
Java中目前最常用的测试框架之一,目前使用广泛的版本是juit4, junit5,这里我们不详细讨论其差异在什么地方,有感兴趣的同学可以自行了解。后面以Junit4 示例来说明Junit的用法。

对后文感兴趣的可以参考单元测试(二)对Junit的讲解:https://editor.csdn.net/md/?articleId=125086038

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雨~旋律

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值