您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
单元测试与重构
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
单元测试与重构
自猿其说Tech
2021-07-07
IP归属:未知
216440浏览
敏捷
敏捷开发
敏捷测试
业务敏捷
### 1 谁要做测试? 做为程序员,大家肯定对“测试”工作的必要性不会产生疑问,但是对于“测试”由谁来做这个问题,我相信很多人的第一直觉会是“测试不就应该是由测试同学做的事吗”。实际上,作为程序员,在交付测试之前都会进行一些基本的测试,确保自己提交的代码是无误的,尽管你可能不认为这个是在做“测试”工作。 作为测试人员,其实只能站在系统外部做功能特性的测试,而软件是由诸多内部单元和模块所构成的,外部测试只能保证功能的准确性,但其实很难覆盖所有的分支和流程。试想一下,你购买的一部分汽车,所有的零部件都未经过测试,只有组装完成后由质检员试开一圈,然后交付给你,你会购买这部汽车吗?但是,在软件工程当中,这种场景时时刻刻就在咱们身边发生。 软件开发中有个重要的概念:软件开发的成本会随着软件开发阶段逐步增加[1],也就是说尽早发现问题的修复成本是最低的,能在需求阶段发现就不要拖到开发阶段,能在开发阶段发现的就不要拖到测试阶段,如果上线后才发现问题,那很可能会演变成一场生产事故。 精益原则有个重要的思想:质量内建(Build quality in)[2],其思想大意为,产品质量不是检测出来的,而是从它诞生的那一刻起就已经在那了。对于软件开发来讲,内建要求我们做好软件开发每个环节,尽早预防,以降低缺陷出现后的修复成本,要减少对创可贴式的补丁(hotfix)的依赖。更为理想的情况是软件质量贯穿开发的全流程,从需求开始的每个环节将“测试”纳入考量,产品经理确定验收标准,开发人员交出开发者测试。 所以,对于程序员来说,只有在Coding时把Test也做好,才有资格说交付了高质量的代码,即测试也是程序员工作中不可分割的一部分。那些在说因为需求比较紧急,所以没来得及做测试的同学,我觉得咱们有必要定义一下开发阶段的DoD 😄 ### 2 测试模型 我们可以将测试分成: - 关注最小程序模块的单元测试、 - 将多个模块组合在一起的集成测试 - 将整个系统组合在一起的系统测试 以上这些不同类型的测试由上到下,覆盖面越来越高,那么这些不同的测试,我们应该怎么搭配更合适呢? #### 2.1 蛋卷模型 一种方法认为,既然高层次的测试覆盖面广,那就多写高层测试,比如系统测试;对于高层次无法覆盖的场景,再由低层次的测试进行补充,比如单元测试;这样就形成了下面这种测试模型: ![](//img1.jcloudcs.com/developer.jdcloud.com/0aa6e972-c338-4782-b3b0-794edd22ed4120210707121634.png) 其实这就是很多团队目前的现状,这是一种费事费力的模型 #### 2.2 金字塔模型 ![](//img1.jcloudcs.com/developer.jdcloud.com/92e924a8-b30f-4d58-a178-ef99ab203d4020210707121705.png) 相比于蛋卷模型,金字塔模型是目前的行业最佳实践,这个模型也是行业大牛Martin Fowler将其发扬光大的[3]。 想要理解金字塔模型,就要理解不同层次测试间的差异,越是底层的测试,牵扯到相关内容越少,而高层测试则涉及面更广。比如单元测试,只关注一个单元,开发完成即可进行测试;而集成测试则是要把好几个单元组装再一起进行测试,测试通过的前提就是每个单元都正确;系统测试则更复杂,集成好所有模块和单元后,甚至还要维护好基础数据才能进行测试。另外,涉及的模块或单元越多,当其中一个发生变化时可能所有的高层测试都会牵涉其中,复杂度进一步提升,定位问题也会比较复杂。反观低层次的测试,因为涉及内容较少,更容易写测试,一旦出现问题,也比较容易定位。 所以,测试金字塔的重点就是越底层的测试应该写得越多。 那么为什么我们的单元测试写起来这么难?这个问题我们留在后面回答。 ### 3 测试驱动开发 什么时候写测试?对于这个问题,我相信很多人会说“肯定是写完代码之后再写测试”,这么说没有错,但是既然测试是程序员要做的工作之一,那么能不能先写测试再写代码呢?我相信大家脑海中肯定是浮现了一个词TDD(Test Driven Development),TDD 就是先写测试后写代码,然而这个理解是错误的,先写测试后写代码的实践应该是“测试先行开发(Test Frist Development)”,虽然只差了一个词“驱动”,那他们之间有什么区别呢? 想要理解测试驱动开发,就要理解什么是“驱动”,我们先看看TFD和TDD的差别。 ![](//img1.jcloudcs.com/developer.jdcloud.com/efa5987e-a045-42f6-b6b8-1ae20fc1fcca20210707121748.png) 如上图,测试先行开发和测试驱动开发,在第一步先写测试,第二步写代码使测试通过,这两步是一样的,区别点在于第三部分“重构”,也就是说测试驱动开发在开发完成,测试跑通之后,还需要再次回到代码上“重构”,因为刚刚只是让代码跑起来了,设计上还有可改之处,新增代码往往存在很多“坏味道”,而重构则是消除坏味道的手段,一旦有了测试,就可以大胆的进行重构,因为任何错误都可以很容易的被捕捉到。 平时总能听到有些人讲“这段代码是有问题,但是现在不敢改”、“这段代码不敢动,所以复制了一份在此基础上进行增改”等等,这些问题总归来讲,就是没有做好单元测试。 #### 3.1 测试驱动设计 很多人排斥单元测试,常见有两个理由:1. 需要“额外”的工作量,时间不够。2. 代码太多不好测。第一点上面已经提过,测试就是程序员工作的一部分;对于第二点,细想一下会发现,其实说的是代码已经写完了,需要后补测试。 如果把“先写代码,后补测试”转换成:先写测试,写代码是为了让测试通过,写出的代码天然具备可测性,是不是就变得简单了呢? 在实际开发工作中,经常能见到长达100行及以上的函数/方法,这种代码我相信没人会说具备可测性。如果写代码时时刻想着可测性,是为了让测试通过,你想写这么长行数的代码都难。了解编写可测试代码的思路,即便你不做 TDD,依然对你改善软件设计有着至关重要的作用。所以,写代码之前,请先想想怎么测。 至于如何写好代码,我在这里推荐一本书《代码整洁之道》 ### 4 简单测试 上面遗留了一个问题,为什么我们的测试写起来这么难呢?这个问题和我们经常写出来长达100行的方法有着直接的关联,因为它太过于复杂了。只有将复杂的测试拆分成简单的测试,测试才有可能做好。 《代码整洁之道》这本书中有个很重要的原则:只做一件事。函数、类、模块,都全神贯注一件事。软件设计的许多原则最终都会归结为这句警句 。当我们定义的方法明明是getXXX,却改变了入参的属性;当我们定义方法时即有返回值,又改变了入参时;当我们在一个for循环里,改变两个值.....都在违反这一重要的原则。 既然测试也是用代码写的,那么如何保证测试代码的准确性呢?我们只有一个方法:把测试写简单,简单到一目了然,不需要证明它的正确性。 一种测试常见的坏味道是没有断言!这种测试就从来没失败过,一看代码竟然是print,这种测试最多也就能证明你曾经debug过这段代码。另一种是有断言,通常是assert 不等于0,true/false一类,看似没问题,但是如果真失败了,需要把调用代码读一遍,甚至debug才能定位到错误。还有一类测试,只能在编写测试时正常执行,后续别人将代码clone下来,无论如何也不能再次正常运行。 **在《单元测试之道》一书中总结道,好的测试应该遵循A-TRIP原则:** - Automatic,自动化 - Thorough,全面,应该尽可能用测试覆盖各种场景 - Repeatable,可重复的 - Independent,独立的 - Professional,专业的 **另外,关于如何测试,也有个[Right]-BICEP缩写:** - Right – Are the results right? 结果是否正确? - B – are all the boundary conditions correct? 所有边界条件都是正确的么? - I – can you check the inverse relationships? 能否检查一下反向关联? - C – can you cross-check results using other means? 能够使用其他手段交叉检查一下结果? - E – can you force error conditions to happen? 是否可以强制错误条件产生? - P – are performance characteristics within bounds? 是否满足性能要求? **其中边界测试有个CORRECT的缩写:** - Conformance(一致性):值是否和预期一致。可以理解为当输入并不是预期的标准数据时,被测试方法是否可以正确输出预期结果(或抛出异常)。 - Ordering(顺序性):值是否像应该的那样是无序或有序的。 - Range(区间性):值是否位于合理的最小值和最大值之间。 - Reference(依赖性):代码是否引用了一些不在代码本身控制范围之内的外部资源,当这些外部资源存在或不存在、满足或不满足时,代码是否可以产生相应的预期结果。 - Existence(存在性):值是否存在(是否为null、0、在一个集合中)。测试方法是否可以处理值不存在的情况。 - Cardinatity(基数性):是否恰好有足够的值。这里的基数指的是计数,测试方法是否可以正确计数,并检查最后的计数值。 - Time(相对或绝对时间性):所有事情的发生是否是有序的、是否在正确的时刻、是否恰好及时。与时间相关问题有:相对时间(时间上的顺序)、绝对时间(消耗的时间和钟表上的时间)、并发问题。例如:方法调用的时间顺序、代码超时、不同的本地时间、多线程同步等 有关更多本书内容可参见[5] ### 5 重构 有句“名言”:Any fool can write code that a computer can understand. Good programmers write code that humans can understand.任何傻瓜都可以编写计算机可以理解的代码。 优秀的程序员会编写人类可以理解的代码。 本质上讲,重构是为了改进已有软件/代码的设计,使软件更易于维护;也就是说,在不改变系统/代码的外部行为前提下,以改进其内部结构的方式改变软件系统的过程,使其更易于理解且修改成本更低。 #### 5.1 何时重构 在Martin《重构》中提到过Don Roberts的三次法则[7],大意为:事不过三,三则重构。结合到日常开发工作中,可以在以下几个节点进行: - 添加功能时一并重构 - 修复BUG时一并重构 - 代码评审时一并重构 是不是有些情况,也不适合重构?通常来讲,如果重构的现有代码过于混乱,重构的成本过高,甚至重来要比重构还要容易则不再适合重构。另外,应尽量避免在临近最后时间点时进行重构,以免推迟计划,这种情况更适合将重构当成一项新的任务进行。 #### 5.2 常见坏味道 - 重复代码 - 大方法 - 大类 - 方法参数列表过长 老马(Martin)Tips:每当我做重构时,首先都是需要为那部分代码构建一组可靠的测试。 测试是必不可少的,因为即使我遵循重构的方法来避免引入错误,但我仍然是人,仍然会犯错误。 因此我需要可靠的测试。 对于如何做好重构,我在这里推荐一本书《重构》 ### 6 参考 Examining the Agile Cost of Change Curvehttp://www.agilemodeling.com/essays/costOfChange.htm 聚焦测试,驱动卓越 https://insights.thoughtworks.cn/foucs-on-testing/ TestPyramid https://martinfowler.com/bliki/TestPyramid.html 《代码整洁之道》 《单元测试之道》 《重构》 《测试驱动开发》 极客时间专栏-10x程序员工作法 ------------ ###### 自猿其说Tech-JDL京东物流技术发展部 ###### 作者:京喜达技术部 郭峰
原创文章,需联系作者,授权转载
上一篇:降低部署人力成本80%!京东私有化环境快速部署平台罗汉堂探索与实践
下一篇:GraphQL实战(3)-构建开发框架
相关文章
浅谈对敏捷的认识
架构研究:研发敏捷与中台架构(论前台bp研发敏捷)
敏捷实践 — 估算
自猿其说Tech
文章数
426
阅读量
2149963
作者其他文章
01
深入JDK中的Optional
本文将从Optional所解决的问题开始,逐层解剖,由浅入深,文中会出现Optioanl方法之间的对比,实践,误用情况分析,优缺点等。与大家一起,对这项Java8中的新特性,进行理解和深入。
01
Taro小程序跨端开发入门实战
为了让小程序开发更简单,更高效,我们采用 Taro 作为首选框架,我们将使用 Taro 的实践经验整理了出来,主要内容围绕着什么是 Taro,为什么用 Taro,以及 Taro 如何使用(正确使用的姿势),还有 Taro 背后的一些设计思想来进行展开,让大家能够对 Taro 有个完整的认识。
01
Flutter For Web实践
Flutter For Web 已经发布一年多时间,它的发布意味着我们可以真正地使用一套代码、一套资源部署整个大前端系统(包括:iOS、Android、Web)。渠道研发组经过一段时间的探索,使用Flutter For Web技术开发了移动端可视化编程平台—Flutter乐高,在这里希望和大家分享下使用Flutter For Web实践过程和踩坑实践
01
配运基础数据缓存瘦身实践
在基础数据的常规能力当中,数据的存取是最基础也是最重要的能力,为了整体提高数据的读取能力,缓存技术在基础数据的场景中得到了广泛的使用,下面会重点展示一下配运组近期针对数据缓存做的瘦身实践。
自猿其说Tech
文章数
426
阅读量
2149963
作者其他文章
01
深入JDK中的Optional
01
Taro小程序跨端开发入门实战
01
Flutter For Web实践
01
配运基础数据缓存瘦身实践
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号