说起单元测试,每个开发人员都很熟悉,但并未得到大家的重视。很多开发人员认为单测属于可有可无,意义不大。或者有时间就写,没时间就算了的情况。甚至认为:“反正有测试同学帮忙把控代码质量,为什么还要开发浪费时间写单测呢?难道不是重复工作么?”这个问题其实很有代表性,很多开发有这个想法,就算他们写了单测,可能也只是敷衍了事或者随意发挥。
这里解释一下前言中的几个关键点:
- 职责的不同。开发人员的职责是在保证质量的前提下完成一个功能的开发,而测试人员的职责是为产品的质量把关,保证项目交付的质量。也就是说保证代码质量是开发这一环节的工作职责之一,在测试阶段发现了代码里的过多缺陷,就说明了一些场景未考虑全面。
- 成本的不同。对于同一个缺陷,在越早期发现,修复的成本就越小,这个道理想必大家都知道。如果很多的质量问题,都需要到测试阶段才能发现,才来返工修复,这对整体项目的时间和资源来说绝对是不小的浪费。
- 角度的不同。对于开发人员来说,写单测更多的是针对单个方法的逻辑的测试,而对于测试人员来说,更多的是针对功能的黑盒测试,而单测能够覆盖到某些特殊场景。
在面向对象语言里,一个方法到一个类,都可以是一个单元,它取决于我们的测试意图。
在Google官方文档中,将测试分为三级,最底层的属于小型测试即单元测试,本文以单测简称之,第二层属于中型测试即集成测试,第三层属于大型测试即UI测试,每一层的比例约为小型测试占 70%,中型测试占 20%,大型测试占 10%。
- 小型测试:针对单个函数的测试,关注其内部逻辑,mock所有需要的服务。小型测试带来优秀的代码质量、良好的异常处理、优雅的错误报告。
- 中型测试:验证两个或多个制定的模块应用之间的交互。
- 大型测试:也被称为“系统测试”或“端到端测试”。大型测试在一个较高层次上运行,验证系统作为一个整体是如何工作的。
你是不是也有这些疑问?
- 单测浪费了太多的时间
- 单测仅仅是证明这些代码做了什么
- 我是很棒的程序员,我是不是可以不进行单测?
- 后面的集成测试将会抓住所有的bug
- 单测的成本效率不高我把测试都写了,那么测试人员做什么呢?
- 公司请我来是写代码,而不是写测试
- 测试代码的正确性,并不是我的工作
- 只是 Bug 少了一点
据统计,大约有80%的错误是在软件设计阶段引入的,并且修正一个软件错误所需的费用将随着软件生命期的进展而上升。错误发现的越晚,修复它的费用就越高,而且呈指数增长的趋势。作为编码人员,也是单测的主要执行者,是唯一能够做到生产出无缺陷程序这一点的人,其他任何人都无法做到这一点。
上面那张图,来自微软的统计数据:bug在单测阶段被发现,平均耗时3.25小时,如果漏到系统测试阶段,要花费11.5小时。
下面这张图,旨在说明两个问题:85%的缺陷都在代码设计阶段产生,而发现bug的阶段越靠后,耗费成本就越高,指数级别的增高。
那么单测的作用到底是什么?意义究竟如何体现?
- 单测可以很好保证代码质量,一个好的单测能够覆盖各种业务场景,在这个前提下该目标单元都能够验证通过,说明该单元是足够健壮的。
- 单测可以一定程度提高代码合理性,当我们发现给一个方法写单测非常困难,比如需要覆盖的业务场景过多,那说明此方法可以在一定程度上继续进行拆分;又比如需要mock的内容过多,那说明此方法违背了单一责任原则,处理了太多的逻辑,那么需要重新设计等等。
- 单测能够有效防止回溯问题的出现,提升回归测试的质量,随着功能的不停增加,全量回归性价比会越来越低,测试人员仅会对主流程进行测试,对于那些不在测试范围内的边界功能,单测能够进行覆盖从而提升回归测试的质量。
- 通过单测快速熟悉代码,单测不仅起到了测试的作用,还是一种很好的“文档”,通过单测代码,我们不需要深入的阅读代码,便能知道这段代码做什么工作,有哪些特殊情况需要考虑,包含哪些业务。
小结:对于单测,我们不搞虚的,希望能实实在在为项目质量保驾护航。
- 针对逻辑复杂、核心的业务去写单测
- 针对痛点、易错的代码去写单测
- 针对不易理解的代码去写单测
- 针对改动较多的部分去写单测
单测是指对软件中的最小可测试单元进行检查和验证,要以类功能作为测试目标的单个或者一连串的函数测试,也就是说,单测可以是对某个类的具体函数的功能、内部逻辑进行验证。
而针对代码复杂性和依赖性,有如下图的原则描述可参考:
- 第一象限复杂依赖多:重构减少依赖,变成复杂依赖少,然后写单测
- 第二象限复杂依赖少:适合写单测
- 第三象限简单依赖少:看情况写
- 第四象限简单依赖多:不用重构,不用单测
这里对第四象限多提一嘴,对它们写单测意义并不大,不要为了提高单测覆盖率,而花费很多时间和精力去写单测,这样得不偿失。
比较好的节奏是:每个功能Sprint的开发周期,单测与具体实现代码同时进行。
首先,单测成长的过程大致可分为如下4个阶段:
- 会写,全员写,不要求写好。
- 写好,有效。关注可测性问题,试点解决
- 可测试性提升。识别可测性问题,熟练使用重构方法进行重构;识别代码架构设计问题;case与业务代码同步编写
- TDD。但这个目标是期望,不能作为必须实现的目标。
我们代码中经常会有日期工具类,下面是对获取有效日期的测试例子。
(1)获取有效日期源码
(2)单元测试代码
我们对边界情况(日期小于0、等于0),和正常情况(日期大于0)分别进行测试,单测代码如下:
当传入日期小于0或等于0时,获取到的有效日期为当前时间戳;
当传入日期大于0时,获取到的有效日期为传入日期。
注:为简化Demo,这里我们假定,程序多次获取当前时间戳是一样的。
(3)单元测试三段式
上面是很经典的测试写法,也有很多人称这种写法为“三段式”,“三段式”包括模拟前提、执行语句、断言结果。
上面测试例子中,time1 和 time2的创建就是模拟前提,例子中最后一行把执行语句和断言合在了一起,分解开来的话就是:
val validDate2 = DateUtils.getValidDate(time2)
Assert.assertEquals(time2, validDate2)
终端应用中,通常会存在页面跳转逻辑,通过传入不同的参数类型,跳转不同页面,下面我们来一起看看。
(1)页面跳转逻辑源码
(2)代码重构
此处我们只关注skipType的输入,会导致什么样的输出,所以对具体实现细节我们可以封装起来,这也是软件设计中最少知道原则的使用。
(3)单元测试代码
可以看到,我们依旧使用的是单元测试三段式:首先,模拟输入,构造skip对象,并且mock真实的goToUgcMapDetail方法调用,做到逻辑隔离与单一验证;然后,调用npcHandlePush方法,根据模拟的输入,应该会调用goToUgcMapDetail方法;最后,验证goToUgcMapDetail方法是否执行,即可。
- bug类指标(间接指标):连续迭代的bug总数趋势、迭代内新建bug的趋势、千行bug率
- 单测的需求覆盖度(50%以上),参与人员覆盖度(80%以上)
- 单测case总数趋势,代码行增量趋势
- 增量代码的行覆盖率(接入层80%,客户端30%)
- 单函数圈复杂度(低于40),单函数代码行数(低于80),扫描告警数
其实,单测的关键成果,并没那么好衡量,需要团队根据自身情况去做出选择。
如果一个对象具有以下特征,比较适合使用mock对象:
- 该对象提供非确定的结果(比如当前的时间或者当前的温度)
- 对象的某些状态难以创建或者重现(比如网络错误或者文件读写错误)
- 对象方法上的执行太慢(比如在测试开始之前初始化数据库)
- 该对象还不存在或者其行为可能发生变化(比如测试驱动开发中驱动创建新的类)
- 该对象必须包含一些专门为测试准备的数据或者方法(后者不适用于静态类型的语言,流行的Mock框架不能为对象添加新的方法。Stub是可以的。)
因此,不要滥用mock(stub),当被测方法中调用其他方法函数,第一反应应该走进去串起来,而不是从根部就mock掉了。
【Android 单元测试,从小白到入门开始_Swuagg的博客-CSDN博客_android 单元测试教程】
【从头到脚说单测——谈有效的单元测试 - 云+社区 - 腾讯云】
到此这篇单元测试的基本要求(单元测试的原则有哪些)的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/te-unit/79945.html