Python实战 群
Java实战 群
长按识别下方二维码,按需求添加
扫码关注添加客服
进Java 群▲
来自丨掘金
https://juejin.im/post/5e32fae3f265da3e12182ce7
前言
1、学习态度
过去当我遇到新知识时,我会问自己一个问题:“这个东西有很多人学吗没有的话我就不学。
但是现在回想一下,这种想法实在是不太理智了,难道股神巴菲特在投资股票时,会考虑这是不是一只热门股票吗p>
他不会,因为真理是掌握在少数人手里的。
我在《把时间当作朋友》中看到这么一个道理:
这个世界上有两种人,一种人是不知道这个知识有什么用,所以决定不学习。
另一种人则是不知道这个知识有什么用,所以决定去学习,随着时间的推移,两者的认知差距会越来越大。
看了这段话后,我就调整了一下自己的心态,对于新知识,我不再抱着怀疑的态度,而是抱着学一学、试一试,说不定真的有用的态度。
这篇文章的内容不是热门知识,但是学习了这门知识后,让我的开发水平有了很大的提升,所以我想分享给大家。
2、目标读者
如果你正在找工作,那这篇文章可能不适合你,因为这不是能让你在面试时拔高的内容。
之所以这么说,是因为就我的了解而言,大部分公司的开发者并不关心这个内容。
大部分的开发者的态度是这样的:
“什么是测试人员做的事情吗是测试人员负责的,我只负责实现。”
当然了,这只是说我接触过的公司和人,不是所有开发者都是这样的,比如我知道的实践单元测试的公司就有 Google 和 ThoughtWorks 。
有很多需求的实现的确是很有挑战的,比如抖音的各种特效。
但是大部分需求的实现是很简单的,所以在大多数时候我们都要考虑如何提升实现的质量。
如果你已经进入工作了,并且对于开发过程中,不断返工修 Bug 的现象感到非常沮丧,那这篇文章就是为你准备的。
3、工作经历
两年前,我在一家创业公司工作,进入那家创业公司后,觉得虽然公司只有几个人,但是他们都有大公司背景,工作态度都很积极。
尤其是后台,是个研究生,在腾讯和搜狗都做过,而且也创业过,他参加创业的那家公司,甚至做到了上亿的流水,很牛。
当时我就在想,说不定我进入这家公司后,很快我们公司就会像新闻上的创业公司一样,一年就融到 1 个亿,三年就上市,很快我就能迎娶白富美,走上人生巅峰。
然后我就工作非常努力,加班,没问题,通宵加班,也没问题。
但实际情况是这样的,老板之前已经招过几个安卓开发了,个个都是巨坑。
上一任的 Android 开发好像在华为做过,有很多年的工作经验,听上去很厉害的样子,实际上也是个大坑。
这里说的坑,指的是开发出来的 App 有各种 Bug ,连基本的使用都有问题,更别说什么用户体验了。
这次我进来,老板期待我能带给公司带来新希望,但我带来的不是新的希望,而是新的绝望。
我开发出来的 App 各种崩溃,怎么点怎么崩,那些修好了这里崩。
当时没有测试人员,开发出来的东西直接交给老板验收,老板看了之后也快崩了。
当时我们几个人针对这个问题开了好几次会,并没有找到什么解决方案,我自己也很纠结、很痛苦,但是当时懵懂无知的我并不知道怎么办。
在挣扎了一段时间后,我离开了这家公司,离开后我就一直在想,难道这真的是软件开发的宿命吗p>
难道软件开发就只能是不断修 Bug 吗什么微信几乎都没有 Bug p>
后面我就围绕这些问题看了很多本书,最后发现这并不是软件开发的宿命,我遇到的问题在几十年前就已经有人遇到了,而且这个坑已经被他们填上了。
前人已经提出了很多填坑的方法,其中一个方法就是这篇文章的主题:
测试驱动开发(TDD, Test-Driven Development)
如果把对知识的运用分为“不知道—知道—做到—做好”这四个水平的话,那么我在 TDD 上 ,也只达到了“做到”的水平。
而写这篇文章的其中一个原因,就是希望自己能通过重新回顾、学习这些知识,让自己进一步靠近“做好”这个水平。
除了找到返工问题的答案以外,这段工作经历还让我明白了另一个道理:
不要老想着我需要什么,多想下我能提供什么。
4. 内容概览
接下来的内容会分为 TDD 入门和 TDD 示例两个部分来讲。
-
TDD 入门
1.1 排雷
1.2 软件内部质量
1.3 TDD 周期
1.4 避免回归
1.5 小结
-
TDD 示例
2.1 基于断言的测试
2.2 被动视图模式
2.3 三个基本准则
2.4 基于交互的测试
文中的代码在文章的最下方会有 GitHub 链接。
一、TDD简介
我是一个非常粗心、编写代码时考虑问题非常不周到的一个人。
在一次工作经历中,我遇到了一位思维比我严谨很多的 iOS 开发者。
当时我问了他一个问题:为什么你能想到我想不到的事情呢p>
他说:这都是经验,等你项目做多了,你也能想到的。
而我想的是,怎么让一个没什么经验的人,在写代码时也能做到周全地考虑问题TDD 就是这个问题的答案。
TDD 用一句话说就是:
写代码只为修复失败的测试。
有了测试作保障,我们可以逐步改进代码的结构,写出可读、可测试的代码、避免过度设计。
而且不用担心优化代码会破坏已有功能,导致软件回归。
回归,指的是软件的功能回到了以前的状态,比如一个功能本来是可以用的,一改就用不了了。
1.1 排雷
TDD 中的测试,不是指手工测试,不是指用手对着 App 点点点,而是指单元测试。
软件开发中的单元测试,和我们学校里的单元测试是非常相似的。
为什么这么说呢p>
学校里的单元测试的目的,就是为了验证我们是否真的理解并记住了书上的知识。
而软件开发中的单元测试的目的,则是为了验证我们是否真的理解我们的代码。
可能大家听到这里会觉得很奇怪,什么是我自己写的,我怎么可能不理解p>
下面给大家看一个例子。
但实际上它可能的执行路径还有另外两条。
在软件内部质量上的主要两个问题是:缺陷多、维护难。
1、缺陷多
软件缺陷,也就是 Bug ,会导致软件不稳定、行为不可预测、完全无法使用,甚至让软件带来的破坏远超过创造的价值。
如果一家餐厅做出来的菜里有蟑螂,那问题很大概率是在厨房,而不是在服务员身上。
但是在软件行业,当做出来的软件有 Bug 时,大家却很有可能把矛头指向测试人员(服务员),而不是开发人员(厨师)。
但问题的根源在软件的内部,软件的外部行为是由内部的一个个函数相互调用而产生的结果。
只有这一个个函数都是健壮的时候,软件的外部行为才有可能按预期工作。
那什么是健壮呢p>
有的时候我会听到一些开发者说这样的话:那是后台给的数据有问题,我的应用才会闪退的。
又或者是:那是前端提交的参数有问题,才会提示异常的。
之所以他们会说这样的话,估计是并不了解软件健壮性的定义:
软件的健壮性,指的是在异常和危险情况下,系统生存的能力。
比如输入参数有误、磁盘故障、 络过载以及有意攻击等行为下,能否不死机、不崩溃。
说白了就是它可以空,你不能崩。
而建立单元测试,用各种方式给我们自己写的函数“找茬”,就可以提升这些函数的健壮性。
传统的测试方法,是在需求开发完成后,再进行黑盒测试。
黑盒测试,就是测试是在不了解软件的内部工作机制的情况下进行的,比如手工测试、使用 Selenium 等自动化测试工具测试。
黑盒测试的问题就在于有很多内部的“雷”,光靠外部的点点点是点不出来的,因为这些类往往是在数据异常的时候才会“爆炸”。
有 Bug 的软件是不能交付的,我们在寻找和修复 Bug 上投入的时间越多,也就意味着我们的开发能力越低。
比如计划用 10 天开发一个模块,结果中途遇到了非常多的 Bug ,修 Bug 花了 5 天,最后开发出来花了 15 天甚至更长的时间。
2、维护难
只有写出可维护性高的代码,我们才能迅速响应业务需求的变化。
好的代码有很多优点,比如良好的设计、各个部分的功能和职责都是清晰的、没有冗余、重复的代码。
而 Bug 通常是由低质量的代码引起的,维护这些代码、基于这些代码进行扩展简直是一场噩梦。
比如重复的代码会让 Bug 的修复变得困难,改完一个地方,还要改其他 4、5 个甚至更多的地方。
当项目中充斥着烂代码时,我们按时交付的压力会越来越大,导致我们写出质量更差的代码,形成恶性循环。
1.3 TDD 周期
一般开发流程是:
设计—实现—(手工)测试。
而 TDD 流程是:
建立测试—编写代码—重构代码。
也就是先建立单元测试、编写实现代码让测试通过,然后再对实现进行重构优化。
1.3.1 TDD 周期
第一步是建立测试而不是编写生产代码,是因为这样可以提高我们代码的可用性。
在还没有写生产代码前,我们能以用户的身份看这个函数好不好用,不用考虑这个函数好不好写。
就像是产品人员在根据需求设计功能时,可以暂时忽略技术可行性,把全部精力用在思考怎么让用户用得更爽。
又比如我们客户端开发者,对于后台提供的接口好不好用会很敏感,后台要求的参数会不会太麻烦,后台返回的字段好不好用,我们都能够快速给出反馈。
但是当面对我们自己写的代码时,我们往往会变成了当局者的身份,只考虑到怎么实现比较容易,而不是怎么实现比较好用。
有的人甚至会把自己写的代码,和自己的尊严关联在一起。
如果被测试人员找出了问题,而且很不给面子的说出来了,就感觉下不了台。
如果我们把自己的身份从当局者转变为旁观者的话,我们就能更理性、更客观地看待自己的代码,从而更好地找出实现可能存在的问题。
另外在建立测试时,我们要注意建立的测试粒度要小,要写“刚好失败”的测试。
而不是一下子写出整个模块的测试,然后花几天写代码让测试通过。
当你熟悉建立测试的方法后,一般建立一个测试需要的时间在几秒钟到几分钟之间,编写生产代码的时间也应该在这个时间区间内。
如果我们编写测试代码或生产代码的时间超过了这个区间,那说明测试的粒度太大了, 要把测试范围和生产代码中的函数进行拆分。
2、编写代码
而第二步编写代码,就是为了让测试从失败的状态变为通过的状态,这时 IDE 会用绿色来表示测试结果,比如下面这样。
在自动化测试中,测试套件就像是一个模具,能套进去的代码就是正常、可工作的代码。
而当我们修改测试代码或生产代码,破坏了测试套件或生产代码的功能后,也就表明软件出现了“回归”的情况。
而为了测试软件是否出现了回归情况的测试,就叫回归测试。
回归测试如果是由手工来执行,会非常麻烦非常复杂,效率非常低,是开发周期中占了非常多时间的一部分工作。
如果能把这部分工作自动化,就能在减少很多测试时间的同时,提升回归测试的质量。
1.5 小结
-
使用 TDD ,通过自己给“找茬”的方式,我们能把程序中大部分的“雷”都排掉。
-
TDD 的三个周期分别是建立测试、编写代码和重构优化。
要注意的是建立的测试粒度要小,最初的实现不需要是最好的实现。
-
使用 TDD 能快速地执行回归测试。
当我们建立了测试集后,测试集就能像烟雾 警器一样为我们工作,让我们能在“火灾”发生前就把火扑灭。
二、TDD示例
2.1 基于断言的测试
常见的两种测试方式是基于断言的测试和基于交互的测试,我们先来看基于断言的测试。
2.1.1 第一个断言
1、建立测试
假设我们现在有这样一条业务规则:手机 必须要是 11 位的,否则要有错误提示。
我们接下来根据这条业务规则来建立一个测试。
下面是用 Kotlin ,以 MVP 的形式建立的登录页,首先建立的是 Presenter 的测试类。
3、运行测试
由于我们刚才并没有做真实的实现,而是直接返回 true ,所以运行测试的结果肯定是失败的。
之所以在明知会失败的情况下,还运行测试,是因为失败的测试结果是一种反馈,是有意义的。
就像是你没做过蛋炒饭,然后你想尝试一下,结果很难吃,这也是一种反馈。
有了反馈,你就可以不断地调整你的做法(实现),最终达到好吃的程度(目标)。
下面我们用真实的实现替换掉原有的硬编码,替换后,测试就通过了。
2.1.3 质量底线
有的朋友可能遇到的情况是时间上不允许做这件事,比如上级说项目这个星期要上线,我不管你怎么弄,你只要上线就行了。
那这时候是不是就应该放弃质量呢p>
在我看来不是的,因为有坑的代码上线后,有多少坑你就要背多少锅。
用户体验被破坏,意味着我们的工作从为用户创造价值,变成了给用户带来麻烦。
一名有职业素养的开发人员,应该坚守质量底线,而且一家不顾产品质量的公司,是不可能有竞争力,不可能有什么长远发展的。
如果是一两次那很正常,但是如果长期是这样,首先要争取跟上级沟通,说明其中的利害关系。
实在不行,就应该考虑换一家公司。
当你坚守质量底线后,换来的就是一个易于维护、易于扩展的项目。
也就是接下来你不用再投入大量的时间“救火”,可以把时间用于开发新功能、学习新技术上,从而提升后续的开发效率。
2.2 被动视图模式
2、模拟响应
4、实现 Presenter
下面的代码是 LoginPresenter 中的实现,这里的实现比较简单,大家看看就好了。
这里 onLogin() 之所以用 on 开头,是因为 View 只能通知 Presenter ,而不是叫 Presenter 干活,在函数的命名上也要体现这一点。
而且在这里还给 isPhoneValid() 方法加了一个 @VisibleForTesting 注解,有了这个注解,我们就可以测试这个私有函数,而 View 是无法调用这个函数的。
点击 test 运行测试任务,测试运行完成后,把目录视图从 Android 切换到 Project,并打开 app/build/reports/tests 目录。
右键 index.html ,选择用浏览器打开。
点击包名后可以看到各个测试的测试结果和运行时间,下面是 LoginPresenterTest 的测试结果。
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!