关于后端:如何写出有效的单元测试

40次阅读

共计 5901 个字符,预计需要花费 15 分钟才能阅读完成。

简介:一个单元测试是一段自动化的代码,这段代码调用被测试的工作单元,之后对这个单元的单个最终后果的某些假如进行校验。单元测试简直都是用单元测试框架编写的;只有产品代码不发生变化,单元测试的后果是稳固的。那么如何写出无效的单元测试呢?

作者 | 王浩 (光酒) 起源 | 阿里开发者公众号什么是单元测试《单元测试的艺术》中对单元测试的定义:一个单元测试是一段自动化的代码,这段代码调用被测试的工作单元,之后对这个单元的单个最终后果的某些假如进行校验。单元测试简直都是用单元测试框架编写的;只有产品代码不发生变化,单元测试的后果是稳固的。为什么须要单元测试在我看来,单元测试的意义能够总结如下三点:单元测试是保障你写的代码是你想要的后果的最无效方法单元测试帮咱们塑造设计单元测试是最好的文档之一单元测试形容了代码的预期行为,能够最无效地保障代码正确运行,缩小代码缺点;因为单元规模较小,当因为代码变更呈现问题的时候,能够帮忙咱们疾速定位问题;有单元测试笼罩的代码,让咱们更有信念,敢于释怀做代码重构;写单元测试的过程往往随同着代码重构,如果发现一段代码单元测试很难写,就须要反思咱们的设计,进而重构促成代码设计的优化,帮忙咱们塑造设计;同时单元测试也是一个最佳的、自动化的、可执行的文档;没有单测笼罩的代码,是很难被保护的。什么是无效的单元测试可读、可保护、可信赖、疾速执行!《单元测试的艺术》中形容优良单元的个性:它应该是自动化的,可反复执行;它应该很容易实现;它应该第二天还有意义;任何人都应该能一键运行它;它应该运行速度很快;它的后果应该是稳固的(如果运行之间没有进行批改的话,屡次运行一个测试应该总是返回同样的后果); 它应该能齐全管制被测试的单元;它应该是齐全隔离的(独立于其余测试的运行);如果它失败了,咱们应该很容易发现什么是期待的后果,进而定位问题所在。可读性“个别程序员写得出计算机能读懂的代码。优良程序员写得出人能读懂的代码”— 马丁·福勒可读的代码才是可保护的;难以浏览和了解的测试用例,最终的后果就是删掉它,因为保护老本过高。可读性高于纯正的性能。可维护性团队内应用一套范式的构造,有助于使之更好用,疾速定位问题;毁灭代码中的坏滋味。可信赖可信赖的含意:测试可反复;测试与依赖环境隔离;只测试不进行验证是不牢靠的测试;在测试类中不要依赖与测试的程序;测试的后果是精准的:校验的精准以及谬误问题的精准定位;疾速执行保障单测疾速执行,缩短反馈时长;为什么无效的单元测试如此重要有效的单元测试是没有意义的,反而会减少保护老本,最终导致单元测试的失败!

 如上图所示,坐标中任意一个点,其与横纵坐标垂直线所造成的矩形面积代表 CI 为团队带来的价值,那么在我看来有两个要害的因素:横坐标是单元测试的根底能力建设,纵坐标则是无效的单元测试;没有无效的单元测试,根底能力做出花来也毫无意义!欠缺的根底能力同时也帮忙咱们更低成本的写出无效的单元测试。如何写无效的单元测试咱们以 Flutter 为例,来一起探讨如何写无效的单元测试;应用测试框架 Flutter 官网提供的测试框架:flutter_testintegration_test 对立的编码约定不论是 AAA(Arrange-Act-Assert)还是 GWT(Given-When-Then),对立的编码约定帮忙保障测试代码的可读性、可维护性。

 应用测试替身测试替身帮忙咱们隔离被测试代码,减速执行速度,保障测试代码是可信赖的;

 Dummy:一种什么也不做的实现形式。接口中的每个办法什么也不做,如果办法有返回值,返回的值尽量靠近 null 或者 0。Stub:Dummy 的一种,Stub 的函数并不返回 null 或 0,而是返回能推动函数沿预约门路被测试的值。Spy:Stub 的一种,它返回测试所需的特定值,推动零碎沿着咱们冀望的门路前行。然而,Spy 能记住对它所做的事,并容许测试询问。Mock:Spy 的一种,它返回测试所需的特定值,推动零碎沿着咱们冀望的门路前行,而且还会记住对它所做的事。不过,Mock 还晓得咱们的预期,基于这些预期,判断测试是否通过;换而言之,Mock 中写明了测试断言。Fake:Fake 是一种模拟器,它实现根底业务规定,这样测试就能要求该 Fake 按须要的门路执行。

 一个测试该当只查看一件事明确测试用意,一旦出错能够精准定位问题;

 一个测试只有一个模仿对象防止过多模仿对象,一个测试用例的校验内容尽量简略;防止冗余测试冗余测试会进步保护老本;

 防止条件逻辑条件逻辑会让你的单元测试更难以保护,出问题不容易排查,不够精准;

 单测须要确定性防止软弱测试,Mock 不确定的依赖:工夫、随机数、并发性、基础设施、现存数据、长久化、网络等等;

 测试疾速执行防止 sleep 等操作,导致测试执行迟缓;

 防止适度指定对于适度指定的探讨,其外围问题就是要咱们判断哪些是单元测试应该笼罩的,哪些是应该留给其余测试伎俩的。如果一个场景,单元测试笼罩之后,导致常常单测失败,须要不断更新保护,那就能够思考不做单元测试笼罩。像素完满是一个典型的、常常拿进去探讨的例子,Flutter 的 Golden Test 就是一个 golden master testing 的例子;《无效的单元测试》中对于像素完满的探讨:像素完满:顾名思义,是一种特定于图形和图像生成的测试坏滋味。它混淆了魔法数字和根本断言,使得测试极难浏览也极其软弱。这种测试简直无奈浏览,因为即便测试在语义上是处于高层概念的,却依然会针对硬编码的底层细节例如像素坐标和色彩来进行断言。指定坐标上的像素是黑还是白,与两个图形是否相连或重叠的概念是有区别的。这种测试极其软弱,因为即便很小的和不相干的输出变动——是否是另一个图像,或图形对象的渲染形式——都足以影响输入、突破测试,谁让你非要准确地查看像素坐标和色彩呢。同样的问题在采纳 golden master 技术时也会遇到,其做法是当时将图像录制下来,并手工查看其正确性,当前再进行测试时就将渲染出的图像与之进行比对。这些可不是咱们违心去保护的测试。咱们不心愿带着这种软弱的精确度去编写测试,而是应用含糊匹配和智能算法来代替繁琐的数值比拟。对于特定场景,Golden Test 是一个十分无效的伎俩,但须要十分审慎的评估;慎用 Golden Test!

 不要写永不失败的测试,不要写没有校验的测试单测须要对明确的逻辑校验,永不失败的测试或者没有校验的测试是不可信赖的。

 测试不要徒有虚名防止测试的形容与测试内容不符;测试后果必须精准;测试该失败的时候肯定要失败!

 测试公有或者受爱护的办法解决思路:将办法变成公共办法;将办法抽取到新类;将办法变成静态方法;将办法成为测试可见办法;防止强制的测试程序依赖测试程序导致测试可靠性变得软弱,将来保护老本变高;

清理测试环境在 teardown 阶段清理测试环境,例如还原全局的 Config、清理创立的文件目录等等;

对立的单测命名、变量命名对立的单测命名能够进步可读性、可维护性;应用有意义的断言断言的错误信息要有意义,呈现问题可能明确谬误的起因;

把单元测试视为“一等公民”测试用例应该被视为“一等公民”:同样须要代码评审,同样须要代码质量检查,确保单元测试的有效性;单元测试代码评审的过程,也是团队同学互相学习的过程,积淀最佳实际的过程。减速执行速度日常对单测执行工夫进行监控,对测试进行性能剖析,优化执行工夫过长的测试用例。测试金字塔测试金字塔是 Mike Cohn 在他的著述《Succeeding with Agile》一书中提出了这个概念。测试金字塔是一个比喻,它通知咱们要把软件测试依照不同粒度来分组。它也通知咱们每个组应该有多少测试。

为了维持金字塔形态,一个衰弱、疾速、可保护的测试组合应该是这样的:写许多小而快的单元测试。适当写一些更粗粒度的测试,写很少高层次的端到端测试。留神不要让你的测试变成冰淇淋或者沙漏那样子,这对保护来说将是一个噩梦,并且跑一遍也须要太多工夫。

防止测试反复在实现测试金字塔时,你也应该牢记这两条根本法令:如果一个更高层级的测试发现了一个谬误,并且底层测试全都通过了,那么你应该写一个低层级测试去笼罩这个谬误;竭尽所能把测试往金字塔上层赶;如果你曾经在低层级测试里笼罩了所有状况,那么再保护一个高层级的测试就没有必要了。警觉沉没老本的思维陷阱,果决摁下删除键。没有理由在不再提供价值的测试上节约宝贵时间。补充单元测试应该从哪里开始单元测试应该及时编写,就算没有实际 TDD,也应该在代码实现之后尽快编写单元测试,防止写出不可测试的代码,也能够让 bug 尽早裸露;但很可怜的,咱们很多时候在刚开始卓越工程,推广单元测试的时候,不得不面对补充单元测试的状况;这相对是一个有挑战的事件。补充单元测试应该从哪里开始?参考测试金字塔,对于根底组件库来说,能够依据具体情况来定;对于业务库来说,第一步倡议从金字塔顶端的测试:优先笼罩回归测试用例中 P0 级别的用例;防止适度指定的端到端测试;适当的契约测试;接下来,从金字塔中间层开始,一直向上、向下补充;可测试的设计该当容易、疾速地为一段代码编写单元测试;可测试的设计,使咱们写出模块化的设计;行动指南为了写出可测试的代码,须要留神以下几点:防止简单的公有办法;防止 final 办法;防止 static 办法;应用 new 要当心;防止构造函数中蕴含逻辑;防止单例;组合优于继承;防止服务查找;基于接口的设计;可测试的代码是否违反了 SOLID 中的开闭准则?可测试的代码设计,有的时候须要防止简单的公有办法或者受爱护的办法,因为这些意味着不可测试;这样的话是不是意味着可测试的设计违反了开闭准则呢?在代码重构的时候,能够认为给对象模型减少了另外一种最终用户——测试用户。另外如果一部分代码切实不心愿裸露,也能够应用 @visibleForTesting 润饰;单元测试与重构写单元测试的过程往往随同着重构;代码重构同样须要单元测试保障代码正确运行。重构须要恪守的纪律:无测试重构无意义,频繁重构、果决重构、坚定重构;继续重构将麻烦扼杀在摇篮;果决重构麻利编程的名言之一。规定很简略:重构时要怯懦。怯懦尝试,怯懦批改,不必胆怯代码。让测试始终能通过建一个绿色安全区,不容许破窗呈现。留条前途仓库打好 tag,以便在须要的时候可能回滚。可测试的代码可测试的代码就是解耦了的代码;可测试的代码帮忙咱们实现更好的形象。做不到 TDD,能够做到测试后行下图是遵循 TDD 三大法令的实际过程;TDD 很弱小,但不肯定实用所有的团队,推广难度很大,学习曲线很高。

TDD 事实上由两个方面组成:测试后行,以及演进式设计;测试后行是十分重要的工程实际,做不到 TDD,能够做到测试后行。在 Kent Beck 的经典名著《解析极限编程》中,提到:尽早测试,常常测试,自动测试!测试后行的实质能力要求是接口的设计能力——是否清晰的定义出设计单元的边界。如何了解单元测试代码覆盖率不要把它们变成治理的指标。这就是你应用覆盖率数字的目标:应用它们作为衡量标准来帮忙你改良,而不是用它们作为惩办团队和使构建失败的棍棒。——《匠艺整洁之道》代码覆盖率的一大禁忌:为了谋求代码覆盖率,只测试不进行验证;一味谋求代码覆盖率,往往写出有效的单元测试,额定减少了保护老本,最终不得不放弃以失败告终。与其谋求代码覆盖率,不如将重点关注在确保写出有意义的测试。积淀最佳实际必须抵赖单元测试有肯定的老本,老本曲线来看,后期比拟高;恰好是这后期的门槛,让很多人望而生畏。在团队内推广的时候,最难的就是写出第一个单元测试;咱们须要积淀最佳实际,帮忙升高写单元测试的老本,让咱们更容易地写出无效的单元测试。我感觉积淀最佳实际最好的办法,就是 Code Review;正如咱们后面所说的,要把单元测试当成是“肯定公民”,在 Code Review 的过程中,互相学习、分享最佳实际,打消有效的单元测试。隔离单元测试与集成测试集成测试是对一个工作单元进行的测试,这个测试对被测试的工作单元没有齐全的管制,并应用该工作单元一个或多个实在依赖,例如工夫、网络、数据库、线程或随机数生成器等。任何测试,如果它的运行速度不快,后果不稳固,或者要用到被测试单元的一个或多个实在依赖,就是集成测试。在日常开发过程,咱们须要建一个绿色安全区:单元测试与集成测试隔离;集成测试不够稳固,运行工夫长等问题,如果不做隔离,日常开发浪费时间和精力保护,最初导致开发人员不再信赖测试。单元测试与 ABTest 单元测试与 ABTest 有什么关系吗?事实上没有什么关系。但肯定水平水平上,它们实质是雷同的,都是保障线上代码品质(当然单测的老本,对于基建、开发者的能力的要求更高);在日常开发中,常常被动为新的代码逻辑减少 AB 开关,一旦线上出问题留一条后路;产生问题的时候往往感叹 AB 开关救我一命;单元测试能够让问题左移,避免问题上线,同样是一道爱护;如果有一天团队同学违心被动减少单元测试来爱护本人的代码,那么单元测试这件事就算比拟胜利了。写在最初从软件工程到卓越工程,单元测试从可选变成了必要;想要实现骨干开发、大库模式,单元测试是前提条件。对于单元测试这件事,我感觉最重要永远是写单元测试的人,优良的团队文化十分重要,没有什么可能真正掂量单元测试做的好坏,有的只是程序员的职业操守。咱们花了很大的篇幅探讨无效单元测试的重要性以及如何写出无效的单元测试,不得不抵赖单元测试有肯定的老本,真正实际仍然须要很多的路要走,须要咱们在实践中定义好单元测试的边界,找到最适宜团队的最佳实际。参考文档《单元测试的艺术》《无效的单元测试》《Succeeding with Agile》《匠艺整洁之道》The Test Pyramid:https://martinfowler.com/arti… Engineering at Google:https://qiangmzsx.github.io/S… 开发者评测局第六期——ModelScope 开源模型社区评测征集令 退出 ModelScope 开源模型社区,应用开源建模神器记录你的建模之旅,抢夺最佳评测奖 AirPods,达摩院文创礼品,阿里巴巴人工智能原子能力大礼包及精品手提包、折扇等限量专属定制好礼。点击这里,查看详情。原文链接:https://click.aliyun.com/m/10… 本文为阿里云原创内容,未经容许不得转载。好文要顶 关注我 珍藏该文 

 

正文完
 0