mirror of
https://github.com/cheetahlou/CategoryResourceRepost.git
synced 2025-11-16 06:03:45 +08:00
mod
This commit is contained in:
217
极客时间专栏/持续交付36讲/测试管理/25 | 代码静态检查实践.md
Normal file
217
极客时间专栏/持续交付36讲/测试管理/25 | 代码静态检查实践.md
Normal file
@@ -0,0 +1,217 @@
|
||||
<audio id="audio" title="25 | 代码静态检查实践" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/c8/ca/c81a960840289f284695db99e59604ca.mp3"></audio>
|
||||
|
||||
你好,我是王潇俊,今天我和你分享的主题是:代码静态检查实践。
|
||||
|
||||
从这次分享开始,我们要正式开始分享测试管理系列这个主题了。测试管理本身是一个很大的范畴,而且和我们之前聊到过的环境、配置等关系密切。
|
||||
|
||||
因为这个专栏我们要解决的最主要的问题是持续交付,所以我在这个测试管理这个系列里面,不会去过多的展开测试本身的内容,而是要把重点放在与持续交付相关的三个重点上:
|
||||
|
||||
<li>
|
||||
代码静态检查;
|
||||
</li>
|
||||
<li>
|
||||
破坏性测试;
|
||||
</li>
|
||||
<li>
|
||||
Mock与回放。
|
||||
</li>
|
||||
|
||||
这三个重点内容,我会分别用一篇文章的篇幅去解释。今天,我们就先从代码静态检查的实践开始吧。
|
||||
|
||||
虽然不同编程语言会使用不同的静态检查工具,但这些静态检查工具的工作原理和检查流程很类似。所以,为了更好地聚焦核心内容,我选择互联网公司常用的 Java 语言的静态检查来展开今天的分享。
|
||||
|
||||
如果你所在公司采用的是其他编程语言,那也没关系,相信你理解了这篇文章中关于原理、流程的内容后,也可以解决你所用具体语言的代码静态检查。如果在这个过程中,你还遇到了其他问题,欢迎你给我留言,我们一起去解决。
|
||||
|
||||
## 为什么需要代码静态检查?
|
||||
|
||||
代码静态检查,即静态代码分析,是指不运行被测代码,仅通过分析或检查源程序的语法、结构、过程、接口等检查程序的正确性,并找出代码中隐藏的错误和缺陷(比如参数不匹配、有歧义的嵌套语句、错误的递归、非法计算、可能出现的空指针引用等等)。
|
||||
|
||||
在软件开发的过程中,静态代码分析往往在动态测试之前进行,同时也可以作为设计动态测试用例的参考。有统计数据证明,在整个软件开发生命周期中,有70%左右的代码逻辑设计和编码缺陷属于重复性错误,完全可以通过静态代码分析发现和修复。
|
||||
|
||||
看到这个统计结果,相信你已经蠢蠢欲动,准备好好执行代码静态检查了,这也是为什么我们要做代码静态检查的原因。
|
||||
|
||||
但是,代码静态检查规则的建立往往需要大量的时间沉淀和技术积累,因此对初学者来说,**挑选合适的静态代码分析工具,自动化执行代码检查和分析,可以极大地提高代码静态检查的可靠性,节省测试成本。**
|
||||
|
||||
## 静态检查工具的优势
|
||||
|
||||
总体来说,静态检查工具的优势,主要包括以下三个方面:
|
||||
|
||||
<li>
|
||||
帮助软件开发人员自动执行静态代码分析,快速定位代码的隐藏错误和缺陷;
|
||||
</li>
|
||||
<li>
|
||||
帮助软件设计人员更专注于分析和解决代码设计缺陷;
|
||||
</li>
|
||||
<li>
|
||||
显著减少在代码逐行检查上花费的时间,提高软件可靠性的同时可以降低软件测试成本。
|
||||
</li>
|
||||
|
||||
目前,已经有非常多的、成熟的代码静态检查工具了。其中,SonarQube 是一款目前比较流行的工具,国内很多互联网公司都选择用它来搭建静态检查的平台。
|
||||
|
||||
SonarQube采用的是B/S架构,通过插件形式,可以支持对Java、C、C++、JavaScript等二十几种编程语言的代码质量管理与检测。
|
||||
|
||||
Sonar通过客户端插件的方式分析源代码,可以采用IDE插件、Sonar-Scanner插件、Ant插件和Maven插件等,并通过不同的分析机制完成对项目源代码的分析和扫描,然后把分析扫描的结果上传到Sonar的数据库,之后就可以通过Sonar Web界面管理分析结果。
|
||||
|
||||
## 静态代码检查近五年的发展状况
|
||||
|
||||
既然静态检查工具的优势如此明显,那么我们就一起看看在实际场景下,这些工具的实施情况又如何呢。
|
||||
|
||||
自2013年以来,国内的大型互联网公司已开始积极地搭建持续交付环境,并如火如荼地开展持续交付的实践。在这个过程中,为了获得更高的投入产出比,实施团队通常会组织各个业务线的负责人,共同确立一套通用的交付流程。
|
||||
|
||||
同时,静态代码检查工具发展迅速,加之各大互联网公司全力追求效率的综合作用,于是持续交付流程除了启用代码静态检查工具外,还发生了如下变化:
|
||||
|
||||
- 从某些团队开展静态检查到所有团队都开展静态检查;
|
||||
- 持续交付系统从缺少静态检查到强制静态检查;
|
||||
- 从借用其他公司的检查规则到形成自己的检查规则。
|
||||
|
||||
由此可见,代码静态检查已经从可有可无变得不可或缺了,已经从部分实施进入到了全体实施的阶段。
|
||||
|
||||
## 设定科学的检查流程
|
||||
|
||||
既然代码静态检查已经变得不可或缺了,那么你自然需要明白一个问题,即如何才能把它全面实施起来。
|
||||
|
||||
在持续交付实践中,我们鼓励尽早地发现代码问题。为了达到这样的效果,静态检查相关的流程可设定如下:
|
||||
|
||||
<li>
|
||||
鼓励开发人员在开发环境(不管是IDE还是编辑器加命令行)下执行静态检查;
|
||||
</li>
|
||||
<li>
|
||||
不管采用的是主干开发还是特性分支开发的分支策略,都尽可能地在代码合入主干之前,通过静态检查;
|
||||
</li>
|
||||
<li>
|
||||
没有通过静态检查的产品包,不允许发布到线上或用户验证环境。
|
||||
</li>
|
||||
|
||||
整个流程可以用下面这张图来表示。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/9f/4c/9fdc60286de2cf413423a20f4a35634c.png" alt="" />
|
||||
|
||||
其中,S2和S3这两个环节,我们可以借助持续交付系统进行强制检查来完成。
|
||||
|
||||
这三个环节的检查,我需要特别说明两点:
|
||||
|
||||
<li>
|
||||
公司或团队通常会有一个公共检查规则的最小集合(简称Rules),不管哪个步骤的检查,至少得保证通过这个最小集合的检查。如果采用SonarQube作为静态检查的管理平台,那么可以把这个Rules配置为一个Profile。利用这样一个机制,你可以很方便地管理规则配置。
|
||||
</li>
|
||||
<li>
|
||||
<p>不管是开发环境还是持续交付系统,都需要及时、方便地获取到这个统一的Rules。这也正是SonarQube 在努力实现的,它推出的IDE插件SonarLint,只需简单的几步配置就能同步Sonar服务最新的 Profile。<br />
|
||||
虽然,目前SonarLint还不能完全替代 FindBugs、PMD和Checkstyle这三个最常用的静态检查工具,但是我们可以预见,类似SonarLint这样的IDE插件,在开发人员群体中是颇受欢迎的。你只需安装一个插件就能涵盖所有的静态检查规则,而且可以毫不费力地实时获取公司统一的检查标准。</p>
|
||||
</li>
|
||||
|
||||
## 跳过检查的几类方式
|
||||
|
||||
为持续交付体系搭建好静态检查服务并设置好Rules后,你千万不要认为事情结束了,直接等着看检查结果就行了。因为,通常还会有以下问题发生:
|
||||
|
||||
<li>
|
||||
代码规则可能不适合程序语言的多个版本;
|
||||
</li>
|
||||
<li>
|
||||
第三方代码生成器自动产生的代码存在问题,该怎么略过静态检查;
|
||||
</li>
|
||||
<li>
|
||||
静态检查受客观情况的限制,存在误报的情况;
|
||||
</li>
|
||||
<li>
|
||||
某些规则对部分情况检查得过于苛刻;
|
||||
</li>
|
||||
<li>
|
||||
其他尚未归类的不适合做静态检查的问题。
|
||||
</li>
|
||||
|
||||
其实,这些问题都有一个共同特点:静态检查时不该报错的地方却报错了,不该报严重问题的地方却报了严重问题。
|
||||
|
||||
于是,我们针对这个共性问题的处理策略,可以分为三类:
|
||||
|
||||
<li>
|
||||
把某些文件设置为完全不做静态检查;
|
||||
</li>
|
||||
<li>
|
||||
把某些文件内部的某些类或方法设置为不做某些规则的检查;
|
||||
</li>
|
||||
<li>
|
||||
调整规则的严重级别,让规则适应语言的多个版本。
|
||||
</li>
|
||||
|
||||
这样就可以提高静态检查的准确度了,接下来我们需要考虑的问题就是提高静态检查的效率了。
|
||||
|
||||
## 如何提高静态检查的效率?
|
||||
|
||||
提高静态检查的效率的重要性,可以概括为以下两个方面:
|
||||
|
||||
- 其一,能够缩短代码扫描所消耗的时间,从而提升整个持续交付过程的效率;
|
||||
- 其二,我们通常会采用异步的方式进行静态检查,如果这个过程耗时特别长的话,会让用户产生困惑,从而质疑执行静态检查的必要性。
|
||||
|
||||
那么,怎么才能提升静态检查的效率呢?
|
||||
|
||||
**除了提升静态检查平台的处理能力外,在代码合入主干前采用增量形式的静态检查,也可以提升整个静态检查的效率**。增量静态检查,是指只对本次合入涉及的文件做检查,而不用对整个工程做全量检查。
|
||||
|
||||
当然,为了有效保证整个工程项目的代码质量,持续交付系统通常会在版本发布到用户验证环境或者上线之前,对整个工程进行全量检查。
|
||||
|
||||
这样做,既能保证产品上线的质量,又可以提高集成过程中的检查效率。
|
||||
|
||||
## 如何制定规则?
|
||||
|
||||
如果你要在实际工作中制定自己的个性化规则,又该如何进行呢?
|
||||
|
||||
在实践中,日常的定制规则往往有两种方式:
|
||||
|
||||
<li>
|
||||
从已有的规则集合中挑选团队适用的规则,必要情况下调整规则的严重等级和部分参数;
|
||||
</li>
|
||||
<li>
|
||||
基于某个规则框架,编写全新的规则。这种方式需要自行编码,难度成本较大,所以我一般不推荐你采用,确实找不到现成的规则时再采用这种方案。
|
||||
</li>
|
||||
|
||||
## Sonar代码静态检查实例
|
||||
|
||||
了解了代码静态检查的理论知识,我们现在就来具体实践一下。你可以从中体会,如何搭建一套Sonar服务,并把它与实际流程结合起来。
|
||||
|
||||
第一步:搭建Sonar服务,安装CheckStyle 等插件。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/46/71/46547bc859cdd27115d4d7b4d1de7071.png" alt="" />
|
||||
|
||||
第二步:设置统一的 Java 检查规则。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/77/67/77af0b0de82daa62dabeea8f6e643f67.png" alt="" />
|
||||
|
||||
第三步:在IDE中安装SonarLint插件后,就可以使用SonarSource的自带规则了。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/94/34/943983a416e2b3e651bebd097cf09634.png" alt="" />
|
||||
|
||||
第四步:如果SonarLint的检查规则不能满足开发环境的要求,你可以执行相关的mvn命令,把检查结果吐到Sonar服务器上再看检查结果,命令如下:
|
||||
|
||||
```
|
||||
mvn org.sonarsource.scanner.maven:sonar-maven-plugin:3.2:sonar -f ./pom.xml -Dsonar.host.url=sonar服务器地址 -Dsonar.login=账号名称 -Dsonar.password=账号密码 -Dsonar.profile=检查规则的集合 -Dsonar.global.exclusions=排除哪些文件 -Dsonar.branch=检查的分支
|
||||
|
||||
```
|
||||
|
||||
第五步:在GitLab 的Merge Request 中增加Sonar静态检查的环节,包括检查状态和结果等。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/8d/ca/8dae6234f5003bee410fca4ae9dfb3ca.png" alt="" />
|
||||
|
||||
第六步:发布到用户验证环境(UAT)前,先查看静态检查结果。如果没有通过检查,则不允许发布。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/94/92/94cb9ec4481d904232f6e5938fde8192.png" alt="" />
|
||||
|
||||
通过上面这六步,一套代码静态检查机制就基本被构建起来了。
|
||||
|
||||
## 总结
|
||||
|
||||
在分享和你分享代码静态检查实践这个主题时,我分享了近五年国内的大型互联网公司在持续交付实践中摸爬滚打的经验。
|
||||
|
||||
从这五年的发展实践中,我们可以清楚地看到,越来越多的研发团队把静态检查作为了一个不可或缺的环节,这也确实帮助研发团队提升了代码质量。
|
||||
|
||||
当然,机器是死的,人是活的,我们千万不要过分迷信静态检查的结果,还要时刻擦亮眼睛,看看是否存在误报等问题。
|
||||
|
||||
## 思考题
|
||||
|
||||
<li>
|
||||
为什么代码静态检查应尽量在开发前期就实施?
|
||||
</li>
|
||||
<li>
|
||||
在你看来,一款好的静态检查工具或一套好的静态检查系统,应该具备哪些特点?
|
||||
</li>
|
||||
|
||||
感谢收听,欢迎你给我留言。
|
||||
|
||||
|
||||
143
极客时间专栏/持续交付36讲/测试管理/26 | 越来越重要的破坏性测试.md
Normal file
143
极客时间专栏/持续交付36讲/测试管理/26 | 越来越重要的破坏性测试.md
Normal file
@@ -0,0 +1,143 @@
|
||||
<audio id="audio" title="26 | 越来越重要的破坏性测试" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/cd/07/cd27238a7e268bb23a1324ce81a4a707.mp3"></audio>
|
||||
|
||||
你好,我是王潇俊。今天我和你分享的主题是:越来越重要的破坏性测试。
|
||||
|
||||
其实,持续交付中涉及到的与测试相关的内容,包括了单元测试、自动化测试、冒烟测试等测试方法和理念,我为什么我把破坏性测试拿出来,和你详细讨论呢?
|
||||
|
||||
原因无非包括两个方面:
|
||||
|
||||
- 其一,单元测试等传统测试方法,已经非常成熟了,而且你肯定也非常熟悉了;
|
||||
- 其二,破坏性测试,变得越来越重要了。
|
||||
|
||||
那么,破坏性测试到底是因为什么原因变得原来越重要呢?
|
||||
|
||||
随着SOA、微服务等架构的演进,分布式系统对测试的要求越来越高,不再像传统的单体应用测试一样,可以很容易地无缝嵌入到持续交付体系中。因为分布式系统的测试不仅需要大量的前提准备,还存在着非常严重的服务依赖问题。
|
||||
|
||||
这就使得分布式系统的测试工作,除了要关注运行的应用本身外,还要考虑测试环境的因素。
|
||||
|
||||
很快,我们就发现,破坏性测试可以解决分布式系统测试的这些难题,而且还可以帮助我们解决更多的问题。它可以弥补传统持续交付体系只关注代码或应用本身,而忽略其他外部因素影响运行中代码的问题。而且,破坏性测试还能很好地证明整个分布式系统的健壮性。
|
||||
|
||||
所以,与其老生长谈一些传统的测试方法,不如我们一起看看更新鲜、更好用的破坏性测试。
|
||||
|
||||
## 什么是破坏性测试?
|
||||
|
||||
顾名思义,破坏性测试就是通过有效的测试手段,使软件应用程序出现奔溃或失败的情况,然后测试在这样的情况下,软件运行会产生什么结果,而这些结果又是否符合预期。
|
||||
|
||||
这里需要注意的是,我们需要使用的测试手段必须是有效的。为什么这样说呢,有两点原因。
|
||||
|
||||
**第一,破坏性测试的手段和过程,并不是无的放矢,它们是被严格设计和执行的**。不要把破坏性测试和探索性测试混为一谈。也就是说,破坏性测试不应该出现,“试试这样会不会出问题”的假设,而且检验破坏性测试的结果也都应该是有预期的。
|
||||
|
||||
**第二,破坏性测试,会产生切实的破坏作用,你需要权衡破坏的量和度**。因为破坏不仅仅会破坏软件,还可能会破坏硬件。通常情况下,软件被破坏后的修复成本不会太大,而硬件部分被破坏后,修复成本就不好说了。所以,你必须要事先考虑好破坏的量和度。
|
||||
|
||||
## 破坏性测试的流程与用例设计
|
||||
|
||||
说到底,破坏性测试还是一种人为、事先设计的测试方法,所以它的流程与普通的软件测试流程基本一致:都包括设计测试用例、开发测试脚本、执行测试脚本、捕获缺陷、报告缺陷的过程。
|
||||
|
||||
破坏性测试与普通测试流程,唯一不同的是,绝大部分普通测试可以在测试失败后,继续进行其他的测试;而破坏性测试,则有可能无法恢复到待测状态,只能停止后续的测试。
|
||||
|
||||
所以,在持续交付的哪个步骤和阶段执行破坏性测试,就非常讲究了,你需要经过严密地设计和预判。
|
||||
|
||||
所以,在设计破坏性测试的测试用例时,我们通常会考虑两个维度:
|
||||
|
||||
**第一个维度是,一个破坏点的具体测试,即设计一个或一组操作,能够导致应用或系统奔溃或异常**。此时,你需要注意两个问题:
|
||||
|
||||
<li>
|
||||
出现问题后的系统或软件是否有能力按预期捕获和处理异常;
|
||||
</li>
|
||||
<li>
|
||||
确认被破坏的系统是否有能力按照预期设计进行必要的修复,以确保能够继续处理后续内容。
|
||||
</li>
|
||||
|
||||
**第二个维度是,整个系统的破坏性测试**,我们通常会采用压力测试、暴力测试、阻断链路去除外部依赖等方法,试图找到需要进行破坏性测试的具体的点。
|
||||
|
||||
这两个维度的测试方法、流程基本一致,区别只是第二维度的测试通常不知道具体要测试的点,所以破坏范围会更大,甚至可能破坏整个系统。
|
||||
|
||||
## 破坏性测试的执行策略
|
||||
|
||||
由于具有切实的破坏力这个特点,我们在执行破坏性测试时需要考虑好执行策略,以避免发生不可挽回的局面。
|
||||
|
||||
一般情况下,在发布前执行破坏性测试相对比较安全。但这也不是绝对的,比如你一不小心把UAT等大型联调环境搞坏了,其代价还是很可观的。
|
||||
|
||||
因此,**绝大部分破坏性测试都会在单元测试、功能测试阶段执行。而执行测试的环境也往往是局部的测试子环境。**
|
||||
|
||||
那么问题又来了,真实环境要比测试子环境更复杂多变,在测试子环境进行的破坏性测试真的有效吗?这真是一个极好的问题。
|
||||
|
||||
所以,最近几年,技术圈衍生出一个很流行的理论:混沌工程。
|
||||
|
||||
## 混沌工程
|
||||
|
||||
随着分布式系统架构的不断进步,传统的破坏性测试也越发捉襟见肘,最主要的问题有两个:
|
||||
|
||||
第一,它被设计得太严格,以至于失真了。而真正有破坏力的故障,都是随机的、并行的、胡乱的。
|
||||
|
||||
第二,它覆盖不了生产环境,只能做到类似抽样检验的能力,且很难重复和持续。
|
||||
|
||||
所以,混沌工程的理论就应运而生了。
|
||||
|
||||
混沌工程是在分布式系统上建立的实验,其目的是建立对系统承受混乱冲击能力的信心。鉴于分布式系统固有的混乱属性,也就是说即使所有的部件都可以正常工作,但把它们结合后,你还是很难预知会发生什么。
|
||||
|
||||
所以,我们需要找出分布式系统的这些弱点。我把这些弱点归为了以下几类:
|
||||
|
||||
- 当服务不可用时,不可用或不完整的回退能力;
|
||||
- 不合理的设置超时时间引起的重试风暴;
|
||||
- 依赖服务接收过多的流量,从而导致中断;
|
||||
- 由单个故障点引起的级联故障;
|
||||
- ……
|
||||
|
||||
我们要避免这些弱点在生产过程中影响客户,所以需要一种方法来探知和管理这些系统固有的混乱,经实践证明,通过一些受控实验,我们能够观察这些弱点在系统中的行为。这种实验方法,就被叫作混沌工程。
|
||||
|
||||
说到具体的实验方法,需要遵循以下4个步骤,即科学实验都必须遵循的4个步骤:
|
||||
|
||||
<li>
|
||||
将正常系统的一些正常行为的可测量数据定义为“稳定态”;
|
||||
</li>
|
||||
<li>
|
||||
建立一个对照组,并假设对照组和实验组都保持“稳定态”;
|
||||
</li>
|
||||
<li>
|
||||
引入真实世界的变量,如服务器崩溃、断网、磁盘损坏等等;
|
||||
</li>
|
||||
<li>
|
||||
尝试寻找对照组和实验组之间的差异,找出系统弱点。
|
||||
</li>
|
||||
|
||||
“稳定态”越难被破坏,则说明系统越稳固;而发现的每一个弱点,则都是一个改进目标。
|
||||
|
||||
混沌工程也有几个高级原则:
|
||||
|
||||
<li>
|
||||
**使用改变现实世界的事件**,就是要在真实的场景中进行实验,而不要想象和构造一些假想和假设的场景;
|
||||
</li>
|
||||
<li>
|
||||
**在生产环境运行**,为了发现真实场景的弱点,所以更建议在生产环境运行这些实验;
|
||||
</li>
|
||||
<li>
|
||||
**自动化连续实现**,人工的手工操作是劳动密集型的、不可持续的,因此要把混沌工程自动化构建到系统中;
|
||||
</li>
|
||||
<li>
|
||||
**最小爆破半径**,与第二条配合,要尽量减少对用户的负面影响,即使不可避免,也要尽力减少范围和程度。
|
||||
</li>
|
||||
|
||||
这样,就更符合持续交付的需求和胃口了。
|
||||
|
||||
## Netflix公司的先驱实践
|
||||
|
||||
Netflix为了保证其系统在AWS上的健壮性,创造了Chaos Monkey,可以说是混沌工程真正的先驱者。
|
||||
|
||||
Chaos Monkey会在工作日期间,随机地杀死一些服务以制造混乱,从而检验系统的稳定性。而工程师们不得不停下手头工作去解决这些问题,并且保证它们不会再现。久而久之,系统的健壮性就可以不断地被提高。
|
||||
|
||||
Netflix公司有一句名言,叫作“避免失败的最好办法就是经常失败”。所以,Chaos Monkey会在日常反复持续执行,真正地持续融合在系统中。这,也为其持续交付中的测试提供了很好的借鉴。
|
||||
|
||||
## 总结
|
||||
|
||||
破坏性测试能够很好地测试分布式系统的健壮性,但也因为其破坏特点,使得它在持续交付中无法显示真正的威力;而混沌工程的提出,很好地解决了这个问题,使破坏性测试的威力能够在持续交付过程中被真正发挥出来。
|
||||
|
||||
混沌工程的一个典型实践是,Netflix公司的Chaos Monkey系统。这个系统已经证明了混沌工程的价值和重要性,值得我们借鉴。
|
||||
|
||||
## 思考题
|
||||
|
||||
你是否考虑过要在自己的公司引入Chaos Monkey?如果要引入的话,你又需要做些什么准备呢?
|
||||
|
||||
感谢你的收听,欢迎给我留言。
|
||||
|
||||
|
||||
180
极客时间专栏/持续交付36讲/测试管理/27 | 利用Mock与回放技术助力自动化回归.md
Normal file
180
极客时间专栏/持续交付36讲/测试管理/27 | 利用Mock与回放技术助力自动化回归.md
Normal file
@@ -0,0 +1,180 @@
|
||||
<audio id="audio" title="27 | 利用Mock与回放技术助力自动化回归" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/11/cc/1118f433d5bf709445882867b2a64ccc.mp3"></audio>
|
||||
|
||||
你好,我是王潇俊。今天我和你分享的主题是:利用Mock与回放技术助力自动化回归。
|
||||
|
||||
在《代码静态检查实践》和《越来越重要的破坏性测试 》这次的分享中,我介绍了对持续交付有重大影响的两个测试类型,即静态代码检查和破坏性测试。
|
||||
|
||||
你可能已经发现,这两种测试正好适用于持续集成和测试管理的一头、一尾:
|
||||
|
||||
- 静态代码检查,适合在所有其他测试类型开始之前优先进行,把住第一关;
|
||||
- 破坏性测试,则适用于集成或验收测试之后,甚至是对系统进行持续长久的测试。
|
||||
|
||||
那么,我们现在再一起来看看,持续交付过程中还有哪些测试方法,以及还有哪些问题和难点吧。
|
||||
|
||||
## 持续交付中的测试难点
|
||||
|
||||
其实,对于持续交付中的测试来说,自动化回归测试是不可或缺的,占了很大的测试比重。而进行自动化回归测试,就始终会有“三座大山”横在你面前。
|
||||
|
||||
**“第一座大山”:测试数据的准备和清理。**
|
||||
|
||||
通常情况下,回归测试的用例是可以复用的,所以比较固定,结果校验也比较确定。而如果要实现回归测试的自动化,就需要保证每次测试时的初始数据尽量一致,以确保测试脚本可复用。
|
||||
|
||||
如果每次的数据都不同,那么每次的测试结果也会受到影响。为了做到测试结果的可信赖,就有两种方法:
|
||||
|
||||
- 一种是,每次全新的测试都使用全新初始化数据;
|
||||
- 另一种是,在测试完成后,清除变更数据,将数据还原。
|
||||
|
||||
但是,这两种方法的实现,都比较麻烦,而且很容易出错。
|
||||
|
||||
**“第二座大山”:分布式系统的依赖。**
|
||||
|
||||
分布式系统因为有服务依赖的问题,所以进行一些回归测试时,也会存在依赖的问题。这个问题,在持续交付中比较难解决:
|
||||
|
||||
<li>
|
||||
单元测试时要面对两难选择,测依赖还是不测依赖;
|
||||
</li>
|
||||
<li>
|
||||
集成测试时,如何保证依赖服务的稳定性,或者说排除由稳定性带来的干扰,所以到底是依赖服务的问题,还是被测服务的问题很难确定;
|
||||
</li>
|
||||
<li>
|
||||
真实的业务系统中,往往还存在多层依赖的问题,你还要想办法解决被测应用依赖的服务的依赖服务。
|
||||
</li>
|
||||
|
||||
我的天呢,“这座大山”简直难以翻越。
|
||||
|
||||
**“第三座大山”:测试用例的高度仿真。**
|
||||
|
||||
如何才能模拟出和用户一样的场景,一直困扰着我们。
|
||||
|
||||
如果我们的回归测试不是自己设计的假想用例,而是真实用户在生产环境中曾经发生过的实际用例的话,那么肯定可以取得更好的回归测试效果。那么,有没有什么办法或技术能够帮助我们做到这一点呢?
|
||||
|
||||
如何翻越这“三座大山”,我在这里给你准备了Mock和回放技术这个两大利器,也就是我接下来要和你重点分享的内容。
|
||||
|
||||
## 两大利器之一Mock
|
||||
|
||||
我先来说说什么是Mock:
|
||||
|
||||
>
|
||||
如果某个对象在测试过程中依赖于另一个复杂对象,而这个复杂对象又很难被从测试过程中剥离出来,那么就可以利用Mock去模拟并代替这个复杂对象。
|
||||
|
||||
|
||||
听起来是不是有点抽象?下面这张图就是Mock定义的一个具象化展示,我们一起来看看吧。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e2/9f/e2e79f8ec5f10e7ef3b3c84c8781d39f.png" alt="" />
|
||||
|
||||
在测试过程中,你可能会遇到这样的情况。你要测试某个方法和对象,而这个被测方法和对象依赖了外部的一些对象或者操作,比如:读写数据库、依赖另外一个对象的实体;依赖另一个外部服务的数据返回。
|
||||
|
||||
而实际的测试过程很难实现这三种情况,比如:单元测试环境与数据库的网络不通;依赖的对象接口还没有升级到兼容版本;依赖的外部服务属于其他团队,你没有办法部署等等。
|
||||
|
||||
那么,这时,你就可以利用Mock技术去模拟这些外部依赖,完成自己的测试工作。
|
||||
|
||||
Mock因为这样的模拟能力,为测试和持续交付带来的价值,可以总结为以下三点:
|
||||
|
||||
<li>
|
||||
<p>**使测试用例更独立、更解耦**。利用Mock技术,无论是单体应用,还是分布式架构,都可以保证测试用例完全独立运行,而且还能保证测试用例的可迁移性和高稳定性。为什么呢?<br />
|
||||
因为足够独立,测试用例无论在哪里运行,都可以保证预期结果;而由于不再依赖于外部的任何条件,使得测试用例也不再受到外部的干扰,稳定性也必然得到提升。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**提升测试用例的执行速度**。由于Mock技术只是对实际操作或对象的模拟,所以运行返回非常快。特别是对于一些数据库操作,或者复杂事务的处理,可以明显缩短整个测试用来的执行时间。<br />
|
||||
这样做最直接的好处就是,可以加快测试用例的执行,从而快速得到测试结果,提升整个持续交付流程的效率。</p>
|
||||
</li>
|
||||
<li>
|
||||
**提高测试用例准备的效率**。因为Mock技术可以实现对外部依赖的完全可控,所以测试人员在编写测试用例时,无需再去特别考虑依赖端的情况了,只要按照既定方式设计用例就可以了。
|
||||
</li>
|
||||
|
||||
那么,如何在测试中使用Mock技术呢?
|
||||
|
||||
目前,市场上有很多不同的Mock框架,你可以根据自己的情况进行选择。主要的应用场景可以分为两类:基于对象和类的Mock,基于微服务的Mock。
|
||||
|
||||
**第一,基于对象和类的Mock**
|
||||
|
||||
基于对象和类的Mock,我比较推荐使用的框架是Mockito或者EasyMock。
|
||||
|
||||
Mockito或者EasyMock这两个框架的实现原理,都是在运行时,为每一个被Mock的对象或类动态生成一个代理对象,由这个代理对象返回预先设计的结果。
|
||||
|
||||
这类框架非常适合模拟DAO层的数据操作和复杂逻辑,所以它们往往只能用于单元测试阶段。而到了集成测试阶段,你需要模拟一个外部依赖服务时,就需要基于微服务的Mock粉墨登场了。
|
||||
|
||||
**第二,基于微服务的Mock**
|
||||
|
||||
基于微服务的Mock,我个人比较推荐的框架是Weir Mock 和 Mock Server。这两个框架,都可以很好地模拟API、http形式的对象。
|
||||
|
||||
从编写测试代码的角度看,Weir Mock 和 Mock Server这两种测试框架实现Mock的方式基本一致:
|
||||
|
||||
<li>
|
||||
标记被代理的类或对象,或声明被代理的服务;
|
||||
</li>
|
||||
<li>
|
||||
通过Mock框架定制代理的行为;
|
||||
</li>
|
||||
<li>
|
||||
调用代理,从而获得预期的结果。
|
||||
</li>
|
||||
|
||||
可见,这两种Mock框架,都很容易被上手使用。
|
||||
|
||||
**第三,携程的Mock Service实践**
|
||||
|
||||
在携程,我们一次集成测试,可能依赖的外部服务和数据服务会有几百个,而这几百个服务中很多都属于基础服务,都有被Mock的价值。
|
||||
|
||||
所以,携程借鉴了Mock Server的想法,在整个测试环境中构建了一套Mock Service:所有服务的请求,都会优先通过这套系统;同时,所有服务的返回也会被拦截。这套Mock Service看起来就像是一个巨大的代理,代理了所有请求。
|
||||
|
||||
那么,测试人员只要去配置自己的哪些请求需要被Mock Service代理就可以了,如果请求的入参相同,且Mock Service中存在该请求曾经的返回,则直接被代理。反之,则透传到真正的服务。
|
||||
|
||||
虽然这会增加性能开销,但是对于整体的回归测试来说,价值巨大,而且方便好用、无需编码。
|
||||
|
||||
Mock技术,通过模拟,绕过了实际的数据调用和服务调用问题,横在我们面前的“三座大山”中的其中两座,测试数据的准备和清理、分布式系统的依赖算是铲平了。但是如何解决“第三座大山”呢,即如何做到模拟用户真正的操作行为呢?
|
||||
|
||||
## 两大利器之二“回放”技术
|
||||
|
||||
**要做到和实际用户操作一致,最好的方法就是记录实际用户在生产环境的操作,然后在测试环境中回放。**
|
||||
|
||||
当然,我们要记录的并不是用户在客户端的操作过程,而是用户产生的最终请求。这样做,我们就能规避掉客户端产生的干扰,直接对功能进行测试了。
|
||||
|
||||
**首先,我们一起来看一下如何把用户的请求记录下来。**
|
||||
|
||||
这里我们需要明确一个前提原则,即:我们并不需要记录所有用户的请求,只要抽样即可,这样既可以保持用例的新鲜度,又可以减少成本。
|
||||
|
||||
我们在携程有两种方案来拦截记录用户操作:
|
||||
|
||||
- 第一种方案是,在统一的SLB上做统一的拦截和复制转发处理。这个方案的好处是,管理统一,实现难度也不算太大。但问题是,SLB毕竟是生产主路径上的处理服务,一不小心,就可能影响本身的路由服务,形成故障。所以,我们有了第二种替换方案。
|
||||
<li>第二种方案是,在集群中扩容一台服务器,在该服务器上启动一个软交换,由该软交换负责复制和转发用户请求,而真正的用户请求,仍旧由该服务器进行处理。<br />
|
||||
这个方案比第一种方案稍微复杂了一些,但在云计算的支持下,却显得更经济。你可以按需扩容服务器来获取抽样结果,记录结束后释放该服务器资源。这个过程中,你也不需要进行过多的配置操作,就和正常的扩容配置一样,减少了风险。</li>
|
||||
|
||||
这样,我们就完成了用户行为的拦截记录。而用户行为记录的保存格式,你也可以根据要使用的的回放工具来决定。
|
||||
|
||||
**然后,我们再一起看看回放的多样性。**
|
||||
|
||||
因为回放过程完全由我们来控制,所以除了正常的原样回放外,我们还可以利用回放过程达到更多的目的。
|
||||
|
||||
我们既可以按照正常的时间间隔,按照记录进行顺序回放;也可以压缩回放时间,形成一定的压力,进行回放,达到压力测试的目的。
|
||||
|
||||
而且,如果可以对记录的请求数据做到更精细的管理,我们还可以对回放进一步抽样和删选,比如只回放符合条件的某些请求等等,找出边界用例,利用这些用例完成系统的容错性和兼容性测试。
|
||||
|
||||
当然,你如果希望做到回放的精细管理,那我的建议是根据你的实际业务特性自研回放工具。
|
||||
|
||||
自研回放工具的整体思路其实非常简单,就是读取拦截的访问记录、模拟实际协议、进行再次访问。当然,你还可以给它加上更多额外的功能,比如数据筛选、异常处理、循环重复等等。
|
||||
|
||||
现在,利用“回放”技术,我们也顺利翻越了最后“一座山”,实现了用户行为的高度仿真。
|
||||
|
||||
## 总结
|
||||
|
||||
我以提出问题-分析问题-解决问题的思路,和你展开了今天的分享内容。
|
||||
|
||||
首先,我和你分享了自动化回归测试会遇到的三个难题:测试数据的准备和清理、分布式系统的依赖,以及测试用例的高度仿真。
|
||||
|
||||
我们可以利用Mock技术(即通过代理的方式模拟被依赖的对象、方法或服务的技术),通过不同的框架,解决自动化回归测试的前两个问题:
|
||||
|
||||
- 基于对象和类的Mock,解决一个应用内部依赖的问题;
|
||||
- 基于微服务的Mock,解决应用与应用之间外部依赖的问题。
|
||||
|
||||
然后,我和你分享了携程的“回放技术”,即先通过虚拟交换机,复制和记录生产用户的实际请求,在测试时“回放”这些真实操作,以达到更逼真地模拟用户行为的目的,从而解决了自动化回归测试遇到的第三个问题。
|
||||
|
||||
所以,利用Mock和“回放”技术,我们能够提高自动化回归测试的效率和准确度,从而使整个持续交付过程更顺滑,自动化程度更高。
|
||||
|
||||
## 思考题
|
||||
|
||||
你所在的公司,有没有合理的回归测试过程?如果没有,是为什么呢,遇到了什么困难?通过我今天分享的内容,你将如何去优化这个回归测试的过程呢?
|
||||
|
||||
感谢你的收听,欢迎你给我留言。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user