摘要:写代码归开发攻城狮,测试归测试攻城狮,大部分状况下单方处于“红蓝相持”状态。
一、“开发者测试”就是“开发者来测试”
开发者测试是古代软件工程中十分重要的一环,麻利开发、骨干开发这些先进的项目管理办法和流程都基于欠缺的开发者测试。当每个月甚至每周都要交付一个版本时,不可能投入大量的测试工程师来进行大规模的零碎级别测试,这时候就须要把整个测试金字塔中的绝大部分测试通过自动化来实现。
咱们明天谈开发者测试,什么是“开发者测试”?我司有清晰的开发与测试之分。写代码归开发攻城狮,测试归测试攻城狮,大部分状况下单方处于“红蓝相持”状态。这与我 10 多年前的研发团队情况十分类似。而当初的软件工程,专职的“测试攻城狮”非常少,很多公司开发测试比例大于 10:1,甚至一些部门没有测试攻城狮一说。而测试攻城狮的角色不再是手动跑测试用例的“苦力”,而是治理产品的测试零碎,对产品测试进行布局、剖析演绎功能测试思维导图、设计测试用例及率领研发团队进行测试工作,更像一位“测试专家 / 测试教练”。举个简略的例子,我之前做的产品是在线视频会议合作产品。咱们每天的线上例会就是用本人做的产品,而且会应用本人开发的新功能测试站点来开“站会”。除了花大量的工夫做 dialy update,而后就是测试专家率领团队(包含 PO、架构师、SM、Dev)依照打算来进行集中(半个小时)的测试。所以说“开发者测试”就是“开发者来测试”,而咱们传统的泛滥测试工程师面临三种前途:成长、转型、淘汰。“测试专家”在我的项目中的话语权也很高,我之前的公司应用骨干开发,有个“一进一出”的评审,团队的这种类型的“测试专家”有一票否决权。甚至在公司有 PE 级别的测试专家(相当于我司 20-21 级的技术专家)。
- 一进:对于一个性能是否可能进入 release branch,在 release branch 关上 feature toggle 进行公布级别的测试。
- 一出:在 engineer release 时,该性能品质合格,容许 feature toggle 进入产线。
二、没有什么测试不能够“自动化测试”
回到测试金字塔,从测试的 ” 开发成本 ”、“执行老本”、“测试覆盖率”、“问题定位”四个维度来看,基于代码级别的白盒测试是及其重要的。
- 开发成本:实现测试用例的老本。
- 执行老本:运行一次测试用例的老本。
- 测试覆盖率:咱们通常所说的 line coverage 和 branch coverage
- 问题定位:测试呈现问题,定位问题的效率
通过测试金字塔及其四个测试维度评估,咱们能够得出:
- 尽可能地多做 Low Level Test:因为他们的执行速度相较于下层的几个测试类型来说快很多且绝对稳固,能够一天屡次执行。一般来说,LLT 灰做到继续集成构建工作中去,甚至在 MR 中执行,保障进入代码仓库的代码品质。
- 在自动化保障的状况下,执行肯定规模的 IT、ST、UI Test:因为他们的执行速度较慢,环境依赖较多,测试先对不稳固。通常在夜里执行一次,阶段性的查看代码品质,反馈代码问题。
- 尽可能地少做大规模的手动测试:因为他们的执行速度相较 LLT 且不够稳固,人力老本较高,也无奈做到一天屡次执行,每次执行都要等很久能力取得反馈后果。然而,他们更贴近实在用户场景,所以要确保肯定周期内或者要害节点工夫执行以下这几个测试以确保软件品质。
当初很多公司曾经迭代公布的周期越来越短,甚至做到了 2 周。手动测试显然无奈适应这种开发模式,而把手动测试的用例通过各种技术计划自动化是惟一路径。代码层面,从底层业务代码到 UI 代码,只有架构设计正当,都是能够做 UT。最顶层的 UI 交互测试,测试用例也是能够自动化运行(大部分 UI 框架都能够通过 accessibility 的接口进行 UI 自动化测试),试想连咱们手机硬件都能够自动化测试“摔手机”这种极其测试,软件有啥做不到?至多有些业界技术大牛公司的某些产品,从代码提交 Merge Request,到产品上产线是能够以天来计算的。这种产品的测试是不会也不可能通过手工测试来实现的。
三、开发者测试”利在当下“,”博得将来“
很多人都认为底层的开发者测试,花了大量的工夫,写了大量的代码,而后来保障性能的正确性,然而每次代码性能或者构造的的变更都要批改测试代码。我手动调试和验证效率更高。确实通过 UT,API 测试来调试代码与本人手动运行调试区别不大,然而通过开发者测试对代码进行调试,从而保障以后我的项目迭代的品质;然而其更重要的作用不是这个。
咱们在 bug 分类中有这样一些名词 : Build Regression Bug,Release Regression Bug。
- Build Regression Bug:开发中同样的性能在新版本呈现一个 bug,然而在之前的版本没有这个问题,咱们叫做 Build Regression Bug.
- Release Regression Bug : 产线上同样的性能在新版本呈现一个 bug,然而在之前的版本没有这个问题,咱们叫做 Release Regression Bug.
咱们每次 commit 到产品中的代码,没有人能够保障其 100% 不会呈现问题,在麻利开发的这种疾速迭代下,不太可能进行全功能的手动测试,所以开发者测试,特地是底层的 UT、API 测试、集成测试,可能很容易的辨认发现这类问题。所以说开发者测试”利在当下“,”博得将来“。
四、TDD 不是必须先写测试代码
对于 TDD,大家的认知是先写测试代码,再在写实现代码,这个说法对也不对。概念上没错,然而如果严格这样做,效率未必最高,这也是 TDD 很难推广的起因之一。咱们把编码实现分成 3 个局部:实现代码、测试代码、调试代码。依照 TDD 的概念时先写测试代码、而后编码,最初调试。咱们通常在代码实现时,一开始不大可能思考的十分清晰,把接口定义的齐全精确,如果严格依照测试、编码、调试来做,测试代码要随着编码频繁批改。当然这自身不是什么大问题,在理论执行过程中,很多人习惯先搭好代码框架、测试框架,而后在编码,测试。等测试实现后在进行调试。所以从华为灰度治理的角度上来说,只有单元测试在调试之前,都能够称作 TDD 开发模式。BTW,当然当初开始风行 BDD,这里想说的是如果连我说的 TDD 都做不到的团队,就不要思考 BDD 了。
(Behavior-Driven Development:BDD 将 TDD 的个别技术和原理与畛域驱动设计 (DDD) 的想法相结合。BDD 是一个设计流动,您能够依据预期行为逐渐构建功能块。BDD 的重点是软件开发过程中应用的语言和交互。行为驱动的开发人员应用他们的母语与畛域驱动设计的语言相结合来形容他们的代码的目标和益处。应用 BDD 的团队应该可能以用户故事的模式提供大量的“性能文档”,并减少可执行场景或示例。BDD 通常有助于领域专家了解实现而不是裸露代码级别测试。它通常以 GWT 格局定义:GIVEN WHEN&THEN。)
五、UT 覆盖率 100% 真的很不好
于单元测试,咱们都会关注一个指标“覆盖率”。不论模块、函数、行、分支覆盖率,必须要有肯定比例的覆盖率。然而每一项你都做到了 100%,那么会给你打“差评”。不是说你做到不好(这里不谈是不是用了正确的形式),而是老本和性价比问题。以最难达到的分支覆盖率(branch coverage),如果要做到 100% 的覆盖率,有些内存调配或者容错爱护的分支都必须测试到,那么你的测试用例思考要翻倍,然而并没有带来的相应价值。甚至一些代码条件分支在程序运行的生命周期内都没有被执行过。
- 模块覆盖率:业务模块代码通过 UT,架构模块代码通过 IT;就从 UT 的覆盖率的角度下来看,不须要去测试架构代码。
- 函数覆盖率:不要为一些无任何逻辑的代码去写 UT。比方咱们有些函数就是 get/set 一个属性,外部实现就用一个变量来赋值保留。这种函数写 UT 就是为了覆盖率而写,没有任何真正的意义。
- 行覆盖率:通常来看平局 80% 高低的行覆盖率是一个正当的指标,有些能够为 0%,而有些须要 100%,如果全副代码都超过 90%,其老本较高,效率较低,不倡议这样做。
- 分支覆盖率:越简单的业务逻辑,越要写更多的测试用例来笼罩,而一些内存调配出错逻辑判断能够不须要测试。
六、用测试来驱动架构和代码品质
这里谈测试驱动架构和代码品质,次要说的是让代码具备欠缺的可测试性,什么是代码的可测试性?简略的说就是类与类之间,模块与模块关系解耦,类与类,模块与模块通过接口编程。依赖的接口通过被动注入式传入,而不是被动获取式。对于程序失常运行时,所传入的接口参数是实在的业务对象,而做测试时,能够传入 fake 的模仿实现。当然不是所有的依赖模块都这样做,一些与业务无关的 Utility Library,或者一些特定的数据对象实现,能够间接调用。
这里咱们讲到了 fake 与 mock,对于 Test Doubles,基本上的概念如下,具体每种代表什么意义,大家能够自行上网搜寻
- 虚构对象(dummy)
- 存根(stub)
- 特务(spy)
- 模仿对象(mock)
- 伪对象(fake)
以后我司大家在做开发者测试时,基本上都在用 Mock Object(实际上在用的过程中,很多是在用入参返回值管制的 Stub)。抛开概念上的问题,尽管通过 Mock 的形式也是能够测试代码,然而实际上用 Mock 基本上意味着咱们的代码关联性较强,模块显示依赖较重,模块移植性较差,特地是 C 语言编程这种问题特地多。以至于当初很多模块根本无法发展单元测试,更多的是在做集成测试。
为什么会呈现这种状况?咱们的高级别的架构师更多的在思考零碎级别的架构设计,把零碎模块,各个利用之间的关系梳理的十分清晰,通常状况下,高级别的架构师能够把零碎模块或利用之间的关系设计的较为正当。然而到了具体的利用业务外部的设计与实现,交给了低级别的架构师来实现。实际上这些模块外部的代码量并不小,很多都是几十万行甚至上百万行的代码量。这时候架构师的程度决定了代码的 Clean Code 品质。我司目前代码上的问题很多不是零碎架构的问题,而是具体业务实现中,短少严格的要求和正当的架构设计。如果在利用级别有一套架构计划来标准,那么至多在模块的接口以及模块与模块之前的交互上也能达到和零碎设计一样较为清晰正当。那么不确定的局部就时每个子模块外部几千上万行的代码局部。
之所以提出用测试驱动架构和代码品质,当给测试提出一个很高的规范时,大家不得不从架构下来解决测试的问题,当测试的问题解决时,Clean Code L3 自然而然就达到了。
七、从“我要写测试依赖代码”到“我要写测试依赖代码”
这句话看着很奇怪,实际上是从根本上去解决单元测试的基本办法。模块之间有依赖,不论是通过 Mock 还是 Fake 的办法,不论架构上如何正当,这种依赖是不能打消的,咱们做到更多的是正当的设计让依赖与模块解耦。第一个“我要写测试依赖代码”,指的是当我实现我的模块时,我要写测试代码来测试。而然我要考的是如何写我的测试依赖。而第二个“我要写测试依赖代码”指的是,当我实现我的代码时,我要思考的是依赖我的模块在测试时,对于我的依赖该怎么解决,” 我要写测试依赖代码”(就是我说的 fake 对象与实现)来帮忙依赖我的模块解决测试依赖问题。
- 思维转变、测试驱动:开发一个模块,不要先思考怎么测试本人,先思考如果他人依赖我,我该怎么让他人更容易测试。模块的提供者,不止要提供模块代码,还要提供一个可复用的 Faked 对象(调用验证;返回值;参数验证;参数解决;性能模仿等)。
- 模块代码的编写者实现本人的 Fake 实现,基本上大部分的代码是由模块编写者来实现,同时这是一个可复用的 Fake 实现。模块依赖方依据本人一些非凡的业务需要来增加本人的代码。基本上遵循 80/20 准则。
- 架构上依赖解耦,通过注入依赖的形式进行接口编程。开发者测试应用 Fake 来实现依赖。
- 当编写测试代码时,所有依赖的接口、依赖的实现都根本实现,更多的关注些测试用例而不是测试依赖。
点击关注,第一工夫理解华为云陈腐技术~