mirror of
https://github.com/cheetahlou/CategoryResourceRepost.git
synced 2026-05-10 19:54:28 +08:00
mod
This commit is contained in:
117
极客时间专栏/软件测试52讲/测试基础知识篇/01 | 你真的懂测试吗?从“用户登录”测试谈起.md
Normal file
117
极客时间专栏/软件测试52讲/测试基础知识篇/01 | 你真的懂测试吗?从“用户登录”测试谈起.md
Normal file
@@ -0,0 +1,117 @@
|
||||
<audio id="audio" title="01 | 你真的懂测试吗?从“用户登录”测试谈起" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/b3/3d/b3029a12149fbbb9ef87ed6704ca883d.mp3"></audio>
|
||||
|
||||
作为专栏的第一篇文章,我选择了一个你耳熟能详的“用户登录”功能作为测试对象,希望通过这样一个简单直白的功能帮助你理解如何做好测试,以及现阶段你需要加强和提高的测试技能。
|
||||
|
||||
可能你会说,“用户登录”这个测试对象也有点太简单了吧,我只要找一个用户,让他在界面上输入用户名和密码,然后点击“确认”按钮,验证一下是否登录成功就可以了。的确,这构成了一个最基本、最典型的测试用例,这也是终端用户在使用系统时最典型的Happy Path场景。
|
||||
|
||||
但是作为测试工程师,你的目标是要保证系统在各种应用场景下的功能是符合设计要求的,所以你需要考虑的测试用例就需要更多、更全面,于是你可能会根据“用户登录”功能的需求描述,结合等价类划分和边界值分析方法来设计一系列的测试用例。
|
||||
|
||||
那什么是等价类划分和边界值分析方法呢?首先,这二者都隶属于最常用、最典型、也是最重要的黑盒测试方法。
|
||||
|
||||
- 等价类划分方法,是将所有可能的输入数据划分成若干个子集,在每个子集中,如果任意一个输入数据对于揭露程序中潜在错误都具有同等效果,那么这样的子集就构成了一个等价类。后续只要从每个等价类中任意选取一个值进行测试,就可以用少量具有代表性的测试输入取得较好的测试覆盖结果。
|
||||
- 边界值分析方法,是选取输入、输出的边界值进行测试。因为通常大量的软件错误是发生在输入或输出范围的边界上,所以需要对边界值进行重点测试,通常选取正好等于、刚刚大于或刚刚小于边界的值作为测试数据。
|
||||
|
||||
从方法论上可以看出来,边界值分析是对等价类划分的补充,所以这两种测试方法经常结合起来使用。
|
||||
|
||||
现在,针对“用户登录”功能,基于等价类划分和边界值分析方法,我们设计的测试用例包括:
|
||||
|
||||
1. 输入已注册的用户名和正确的密码,验证是否登录成功;
|
||||
1. 输入已注册的用户名和不正确的密码,验证是否登录失败,并且提示信息正确;
|
||||
1. 输入未注册的用户名和任意密码,验证是否登录失败,并且提示信息正确;
|
||||
1. 用户名和密码两者都为空,验证是否登录失败,并且提示信息正确;
|
||||
1. 用户名和密码两者之一为空,验证是否登录失败,并且提示信息正确;
|
||||
1. 如果登录功能启用了验证码功能,在用户名和密码正确的前提下,输入正确的验证码,验证是否登录成功;
|
||||
1. 如果登录功能启用了验证码功能,在用户名和密码正确的前提下,输入错误的验证码,验证是否登录失败,并且提示信息正确。
|
||||
|
||||
列出这些测试用例后,你可能已经觉得比较满意了,因为你感觉已经把自己的测试知识都用在这些用例设计中了。
|
||||
|
||||
的确,上面的测试用例集已经涵盖了主要的功能测试场景。但是在一个优秀的测试工程师眼中,这些用例只能达到勉强及格的标准。
|
||||
|
||||
什么?才刚刚及格?如果你有这个想法,那我建议你在继续看下面的内容前,先仔细思考一下,这些测试用例是否真的还需要扩充。
|
||||
|
||||
现在,我跟你分享一下有经验的测试工程师会再增加的测试用例:
|
||||
|
||||
1. 用户名和密码是否大小写敏感;
|
||||
1. 页面上的密码框是否加密显示;
|
||||
1. 后台系统创建的用户第一次登录成功时,是否提示修改密码;
|
||||
1. 忘记用户名和忘记密码的功能是否可用;
|
||||
1. 前端页面是否根据设计要求限制用户名和密码长度;
|
||||
1. 如果登录功能需要验证码,点击验证码图片是否可以更换验证码,更换后的验证码是否可用;
|
||||
1. 刷新页面是否会刷新验证码;
|
||||
1. 如果验证码具有时效性,需要分别验证时效内和时效外验证码的有效性;
|
||||
1. 用户登录成功但是会话超时后,继续操作是否会重定向到用户登录界面;
|
||||
1. 不同级别的用户,比如管理员用户和普通用户,登录系统后的权限是否正确;
|
||||
1. 页面默认焦点是否定位在用户名的输入框中;
|
||||
1. 快捷键Tab和Enter等,是否可以正常使用。
|
||||
|
||||
看完这些用例,你可能会说:“哇塞,原来一个简简单单的登录功能居然有这么多需要测试的点”。但是,你别高兴得太早,“用户登录”功能的测试还没结束。
|
||||
|
||||
虽然改进后的测试用例集相比之前的测试覆盖率的确已经提高了很多,但是站在资深测试人员的角度来看,还有很多用例需要设计。
|
||||
|
||||
经我这么一说,你可能已经发现,上面所有的测试用例设计都是围绕显式功能性需求的验证展开的,换句话说,这些用例都是直接针对“用户登录”功能的功能性进行验证和测试的。
|
||||
|
||||
但是,一个质量过硬的软件系统,除了显式功能性需求以外,其他的非功能性需求即隐式功能性需求也是极其关键的。
|
||||
|
||||
**显式功能性需求(Functional requirement)的含义从字面上就可以很好地理解,指的是软件本身需要实现的具体功能,** 比如“正常用户使用正确的用户名和密码可以成功登录”、“非注册用户无法登录”等,这都是属于典型的显式功能性需求描述。
|
||||
|
||||
那什么是非功能性需求(Non-functional requirement)呢?**从软件测试的维度来看,非功能性需求主要涉及安全性、性能以及兼容性三大方面。** 在上面所有的测试用例设计中,我们完全没有考虑对非功能性需求的测试,但这些往往是决定软件质量的关键因素。
|
||||
|
||||
明白了非功能性需求测试的重要性后,你可以先思考一下还需要设计哪些测试用例,然后再来看看我会给出哪些用例,相信这种方式对你的帮助会更大。
|
||||
|
||||
**安全性测试用例包括:**
|
||||
|
||||
1. 用户密码后台存储是否加密;
|
||||
1. 用户密码在网络传输过程中是否加密;
|
||||
1. 密码是否具有有效期,密码有效期到期后,是否提示需要修改密码;
|
||||
1. 不登录的情况下,在浏览器中直接输入登录后的URL地址,验证是否会重新定向到用户登录界面;
|
||||
1. 密码输入框是否不支持复制和粘贴;
|
||||
1. 密码输入框内输入的密码是否都可以在页面源码模式下被查看;
|
||||
1. 用户名和密码的输入框中分别输入典型的“SQL注入攻击”字符串,验证系统的返回页面;
|
||||
1. 用户名和密码的输入框中分别输入典型的“XSS跨站脚本攻击”字符串,验证系统行为是否被篡改;
|
||||
1. 连续多次登录失败情况下,系统是否会阻止后续的尝试以应对暴力破解;
|
||||
1. 同一用户在同一终端的多种浏览器上登录,验证登录功能的互斥性是否符合设计预期;
|
||||
1. 同一用户先后在多台终端的浏览器上登录,验证登录是否具有互斥性。
|
||||
|
||||
**性能压力测试用例包括:**
|
||||
|
||||
1. 单用户登录的响应时间是否小于3秒;
|
||||
1. 单用户登录时,后台请求数量是否过多;
|
||||
1. 高并发场景下用户登录的响应时间是否小于5秒;
|
||||
1. 高并发场景下服务端的监控指标是否符合预期;
|
||||
1. 高集合点并发场景下,是否存在资源死锁和不合理的资源等待;
|
||||
1. 长时间大量用户连续登录和登出,服务器端是否存在内存泄漏。
|
||||
|
||||
**兼容性测试用例包括:**
|
||||
|
||||
1. 不同浏览器下,验证登录页面的显示以及功能正确性;
|
||||
1. 相同浏览器的不同版本下,验证登录页面的显示以及功能正确性;
|
||||
1. 不同移动设备终端的不同浏览器下,验证登录页面的显示以及功能正确性;
|
||||
1. 不同分辨率的界面下,验证登录页面的显示以及功能正确性。
|
||||
|
||||
说到这里,你还会觉得“用户登录”功能的测试非常简单、不值一提么?一个看似简单的功能测试,居然涵盖了如此多的测试用例,除了要覆盖明确的功能性需求,还需要考虑其他诸多的非功能性需求。
|
||||
|
||||
另外,通过这些测试用例的设计,你也可以发现,**一个优秀的测试工程师必须具有很宽广的知识面,如果你不能对被测系统的设计有深入的理解、不明白安全攻击的基本原理、没有掌握性能测试的基本设计方法,很难设计出“有的放矢”的测试用例。**
|
||||
|
||||
通过“用户登录”功能测试这个实例,我希望可以激发你对测试更多的思考,并且开拓你设计测试用例的思路,以达到抛砖引玉的效果。
|
||||
|
||||
看完了这些测试用例,你可能会说还有一些遗漏的测试点没有覆盖到,这个功能的测试点还不够全面。那么,接下来我再跟你说说测试的不可穷尽性,即绝大多数情况下,是不可能进行穷尽测试的。
|
||||
|
||||
**所谓的“穷尽测试”是指包含了软件输入值和前提条件所有可能组合的测试方法,完成穷尽测试的系统里应该不残留任何未知的软件缺陷。** 因为如果有未知的软件缺陷,你可以通过做更多的测试来找到它们,也就是说你的测试还没有穷尽。
|
||||
|
||||
但是,在绝大多数的软件工程实践中,测试由于受限于时间成本和经济成本,是不可能去穷尽所有可能的组合的,而是采用基于风险驱动的模式,有所侧重地选择测试范围和设计测试用例,以寻求缺陷风险和研发成本之间的平衡。
|
||||
|
||||
## 总结
|
||||
|
||||
首先,对于高质量的软件测试,用例设计不仅需要考虑明确的显式功能性需求,还要涉及兼容性、安全性和性能等一系列的非功能性需求,这些非功能性需求对软件系统的质量有着举足轻重的作用。
|
||||
|
||||
其次,优秀的测试工程师必须具有宽广的知识面,才能设计出有针对性、更易于发现问题的测试用例。
|
||||
|
||||
最后,软件测试的用例设计是不可穷尽的,工程实践中难免受制于时间成本和经济成本,所以优秀的测试工程师需要兼顾缺陷风险和研发成本之间的平衡。
|
||||
|
||||
## 思考题
|
||||
|
||||
从拓展思维的角度,请你思考一下“用户登录”功能是否还可以添加更多的测试用例。基于同样的思路,思考一下你目前工作中的测试用例设计是否需要加入更多的测试点。
|
||||
|
||||
欢迎你给我留言。
|
||||
|
||||
|
||||
162
极客时间专栏/软件测试52讲/测试基础知识篇/02 | 如何设计一个“好的”测试用例?.md
Normal file
162
极客时间专栏/软件测试52讲/测试基础知识篇/02 | 如何设计一个“好的”测试用例?.md
Normal file
@@ -0,0 +1,162 @@
|
||||
<audio id="audio" title="02 | 如何设计一个“好的”测试用例?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/8b/7c/8b665c75cca4355c3dc7de638101fb7c.mp3"></audio>
|
||||
|
||||
在上一篇文章中,我以“用户登录”这一简单直接的功能作为测试对象,为你介绍了如何设计测试用例。现在你应该已经知道,为了保证软件系统的质量,测试用例的设计不仅需要考虑功能性需求,还要考虑大量的非功能性需求。
|
||||
|
||||
那么,今天我会重点和你探讨如何才能设计出一个“好的”测试用例。
|
||||
|
||||
## 什么才算是“好的”测试用例?
|
||||
|
||||
在正式开始讨论之前,我先跟你聊聊,什么才是“好的”测试用例,这个“好”又应该体现在哪些方面。这是一个看似简单实则难以回答的问题,即使深入思考后,也很难有非常标准的答案。
|
||||
|
||||
通常,你的第一反应很可能会是“发现了软件缺陷的测试用例就是好的用例”,我可能会反问你“如果说测试用例发现了缺陷就是好用例,那么在该缺陷被修复后,同样的用例难道就不是好用例了吗?”。
|
||||
|
||||
你可能还会说“发现软件缺陷可能性大的测试用例就是好用例”,这话看起来还是蛮有道理的,但是我同样会反问你“你打算用什么方法来量化测试用例发现缺陷的可能性?”。
|
||||
|
||||
类似地,你可能还会说“发现至今未被发现的软件缺陷的测试用例就是好用例”,那么我想问你的是:如何评估是否还存在未被发现的缺陷?如果软件中根本就没有错误了呢?
|
||||
|
||||
其实,是你定义“好的”测试用例的思路错了,这就有点像“傻子吃烧饼”,连吃五个不饱,吃完第六个终于饱了,于是他说:早知道吃了第六个就会饱,何必吃前面五个呢。细想,他吃的六个烧饼其实是一个整体,一起吃下去才会饱,而你无法找到吃一个就能饱的“好”烧饼。
|
||||
|
||||
对于测试用例其实也是同样的道理,**“好的”测试用例一定是一个完备的集合,它能够覆盖所有等价类以及各种边界值,而跟能否发现缺陷无关。**
|
||||
|
||||
我举一个“池塘捕鱼”的例子,可以帮你更好地理解什么是“好的”测试用例。
|
||||
|
||||
如果把被测试软件看作一个池塘,软件缺陷是池塘中的鱼,建立测试用例集的过程就像是在编织一张捕渔网。“好的”测试用例集就是一张能够覆盖整个池塘的大渔网,只要池塘里有鱼,这个大渔网就一定能把鱼给捞上来。
|
||||
|
||||
如果渔网本身是完整的且合格的,那么捞不到鱼,就证明池塘中没有鱼,而渔网的好坏与池塘中是否有鱼无关。
|
||||
|
||||
## “好的”测试用例必须具备哪些特征?
|
||||
|
||||
一个“好的”测试用例,必须具备以下三个特征。
|
||||
|
||||
<li>
|
||||
**整体完备性:** “好的”测试用例一定是一个完备的整体,是有效测试用例组成的集合,能够完全覆盖测试需求。
|
||||
</li>
|
||||
<li>
|
||||
**等价类划分的准确性:** 指的是对于每个等价类都能保证只要其中一个输入测试通过,其他输入也一定测试通过。
|
||||
</li>
|
||||
<li>
|
||||
**等价类集合的完备性:** 需要保证所有可能的边界值和边界条件都已经正确识别。
|
||||
</li>
|
||||
|
||||
做到了以上三点,就可以肯定测试是充分且完备的,即做到了完整的测试需求覆盖。
|
||||
|
||||
## 三种最常用的测试用例设计方法
|
||||
|
||||
明白了“好的”测试用例的内涵和外延后,我再回过头来给你讲讲,为了能够设计出“好的”测试用例,你通常都要使用哪些设计方法。
|
||||
|
||||
从理论层面来讲,设计用例的方法有很多,如果你去翻阅测试图书或网络教程,会发现一堆让人眼花缭乱的测试方法,比如等价类划分法、边界值分析法、错误推测方法、因果图方法、判定表驱动分析法、正交实验设计方法、功能图分析方法、场景设计方法、形式化方法、扩展有限状态机方法等等,但是从软件企业实际的工程实践来讲,真正具有实用价值并且常用的只有前三种方法。
|
||||
|
||||
当然,对于那些与人的生命安全直接或间接相关的软件,比如飞行控制、轨道交通的列车控制、医疗检测相关的软件或者系统,由于需要达到几近变态的测试覆盖率要求,会采用更多的测试设计方法。但对大多数的软件测试而言,综合使用等价类划分、边界值分析和错误推测这三大类方法就足够了。
|
||||
|
||||
接下来,我会结合实际的例子,给你解释一下这三类方法的核心概念以及在使用时需要注意的问题。
|
||||
|
||||
**第一,等价类划分方法**
|
||||
|
||||
从上一篇文章中你已经知道了,等价类中任意一个输入数据对于揭露程序中潜在错误都具有同等效果。后续**我们只要从每个等价类中任意选取一个值进行测试,就可以用少量具有代表性的测试输入取得较好的测试覆盖结果**。
|
||||
|
||||
现在,我给你看一个具体的例子:学生信息系统中有一个“考试成绩”的输入项,成绩的取值范围是0~100之间的整数,考试成绩及格的分数线是60。
|
||||
|
||||
为了测试这个输入项,显然不可能用0~100的每一个数去测试。通过需求描述可以知道,输入0~59之间的任意整数,以及输入60~100之间的任意整数,去验证和揭露输入框的潜在缺陷可以看做是等价的。
|
||||
|
||||
那么这就可以在0~59和60~100之间各随机抽取一个整数来进行验证。这样的设计就构成了所谓的“有效等价类”。
|
||||
|
||||
你不要觉得进行到这里,已经完成了等价类划分的工作,因为**等价类划分方法的另一个关键点是要找出所有“无效等价类”**。显然,如果输入的成绩是负数,或者是大于100的数等都构成了“无效等价类”。
|
||||
|
||||
在考虑了无效等价类后,最终设计的测试用例为:
|
||||
|
||||
- 有效等价类1:0~59之间的任意整数;
|
||||
- 有效等价类2:59~100之间的任意整数;
|
||||
- 无效等价类1:小于0的负数;
|
||||
- 无效等价类2:大于100的整数;
|
||||
- 无效等价类3:0~100之间的任何浮点数;
|
||||
- 无效等价类4:其他任意非数字字符。
|
||||
|
||||
**第二,边界值分析方法**
|
||||
|
||||
边界值分析是对等价类划分的补充,你从工程实践经验中可以发现,大量的错误发生在输入输出的边界值上,所以需要对边界值进行重点测试,通常选取正好等于、刚刚大于或刚刚小于边界的值作为测试数据。
|
||||
|
||||
我们继续看学生信息系统中“考试成绩”的例子,选取的边界值数据应该包括:-1,0,1,59,60,61,99,100,101。
|
||||
|
||||
**第三,错误推测方法**
|
||||
|
||||
错误推测方法是指基于对被测试软件系统设计的理解、过往经验以及个人直觉,推测出软件可能存在的缺陷,从而有针对性地设计测试用例的方法。这个方法强调的是对被测试软件的需求理解以及设计实现的细节把握,当然还有个人的能力。
|
||||
|
||||
错误推测法和目前非常流行的“探索式测试方法”的基本思想和理念是不谋而合的,这类方法在目前的敏捷开发模式下的投入产出比很高,因此被广泛应用。但是,这个方法的缺点也显而易见,那就是难以系统化,并且过度依赖个人能力。
|
||||
|
||||
比如,Web界面的GUI功能测试,需要考虑浏览器在有缓存和没有缓存下的表现;Web Service的API测试,需要考虑被测API所依赖的第三方API出错下的处理逻辑;对于代码级的单元测试,需要考虑被测函数的输入参数为空情况下的内部处理逻辑等等。由此可见,这些测试用例的设计都是基于曾经遇到的问题而进行的错误推测,很大程度上取决于个人能力。
|
||||
|
||||
在软件企业的具体实践中,为了降低对个人能力的依赖,通常会建立常见缺陷知识库,在测试设计的过程中,会使用缺陷知识库作为检查点列表(checklist),去帮助优化补充测试用例的设计。
|
||||
|
||||
对于中小企业,可能最初的方法就是建立一个简单的wiki页面,让测试工程师完成测试用例的最初设计后对应这个wiki页面先做一轮自检,如果在后续测试中发现了新的点,就会继续完善这个wiki页面。
|
||||
|
||||
对于测试基础架构比较成熟的中大型软件企业,通常会以该缺陷知识库作为数据驱动测试的输入来自动生成部分的测试数据,这部分内容我会在后面的文章中详细介绍。
|
||||
|
||||
## 如何才能设计出“好的”测试用例?
|
||||
|
||||
掌握了最基本的三种设计测试用例的方法,你就相当于拿到了打仗所需要的枪支弹药,接下来就是如何在实战中用这些武器打个大胜仗了。
|
||||
|
||||
**在真实的工程实践中,不同的软件项目在研发生命周期的各个阶段都会有不同的测试类型。** 比如,传统软件的开发阶段通常会有单元测试,软件模块集成阶段会有代码级集成测试,打包部署后会有面向终端用户的GUI测试;再比如,电商网站的测试会分为服务器端基于API的测试、中间件测试、前端GUI测试等。
|
||||
|
||||
**对于每一种不同的测试类型,设计出“好的”测试用例的关注点和方法论可能会有很大的差异,** 有些可能采用黑盒方法,有些可能采用白盒方法,有些还会采用灰盒方法(比如,微服务架构中的测试),所以很难有一套放之四海而皆准的套路。
|
||||
|
||||
所以,在这篇文章中,我仅以最常见、最容易理解的面向终端用户的GUI测试为例,跟你聊聊如何才能设计一个“好的”测试用例。
|
||||
|
||||
面向终端用户的GUI测试,最核心的测试点就是验证软件对需求的满足程度,这就要求测试工程师对被测软件的需求有深入的理解。在我看来,深入理解被测软件需求的最好方法是,测试工程师在需求分析和设计阶段就开始介入,因为这个阶段是理解和掌握软件的原始业务需求的最好时机。
|
||||
|
||||
只有真正理解了原始业务需求之后,才有可能从业务需求的角度去设计针对性明确、从终端用户使用场景考虑的端到端(End-2-End)的测试用例集。这个阶段的测试用例设计,主要目的是验证各个业务需求是否被满足,主要采用基于黑盒的测试设计方法。
|
||||
|
||||
在具体的用例设计时,首先需要搞清楚每一个业务需求所对应的多个软件功能需求点,然后分析出每个软件功能需求点对应的多个测试需求点,最后再针对每个测试需求点设计测试用例。
|
||||
|
||||
这个用例设计过程,你可能觉得有点绕,但是没关系,我以“用户登录”功能的测试用例设计为例,画了一张图来帮你理清这些概念之间的映射关系。
|
||||
|
||||
图中的业务需求到软件功能需求、软件功能需求到测试需求,以及测试需求到测试用例的映射关系,在非互联网软件企业的实践中,通常会使用需求追踪管理工具(比如ALM、DOORS、JIRA、TestLink等)来管理,并以此来衡量测试用例对业务需求、软件功能需求的覆盖率。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f1/f7/f1adcc92da9091037ccc022f29911ef7.png" alt="" />
|
||||
|
||||
具体到测试用例本身的设计,有两个关键点需要你注意。
|
||||
|
||||
<li>
|
||||
**从软件功能需求出发,全面地、无遗漏地识别出测试需求是至关重要的,这将直接关系到用例的测试覆盖率。** 比如,如果你没有识别出用户登录功能的安全性测试需求,那么后续设计的测试用例就完全不会涉及安全性,最终造成重要测试漏洞。
|
||||
</li>
|
||||
<li>
|
||||
<p>**对于识别出的每个测试需求点,需要综合运用等价类划分、边界值分析和错误推测方法来全面地设计测试用例。** 这里需要注意的是,要综合运用这三种方法,并针对每个测试需求点的具体情况,进行灵活选择。<br />
|
||||
以“用户登录”的功能性测试需求为例,你首先应该对“用户名”和“密码”这两个输入项分别进行等价类划分,列出对应的有效等价类和无效等价类,对于无效等价类的识别可以采用错误猜测法(比如,用户名包含特殊字符等),然后基于两者可能的组合,设计出第一批测试用例。<br />
|
||||
等价类划分完后,你需要补充“用户名”和“密码”这两个输入项的边界值的测试用例,比如用户名为空(NULL)、用户名长度刚刚大于允许长度等。</p>
|
||||
</li>
|
||||
|
||||
## 用例设计的其他经验
|
||||
|
||||
除了上面介绍的方法外,我再跟你分享三个独家“秘籍”,希望能够帮你设计出“好的”测试用例集。
|
||||
|
||||
<li>
|
||||
<p>**只有深入理解被测试软件的架构,你才能设计出“有的放矢”的测试用例集,去发现系统边界以及系统集成上的潜在缺陷。**<br />
|
||||
作为测试工程师,切忌不能把整个被测系统看作一个大黑盒,你必须对内部的架构有清楚的认识,比如数据库连接方式、数据库的读写分离、消息中间件Kafka的配置、缓存系统的层级分布、第三方系统的集成等等。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**必须深入理解被测软件的设计与实现细节,深入理解软件内部的处理逻辑。**<br />
|
||||
单单根据测试需求点设计的用例,只能覆盖“表面”的一层,往往会覆盖不到内部的处理流程、分支处理,而没有覆盖到的部分就很可能出现缺陷遗漏。在具体实践中,你可以通过代码覆盖率指标找出可能的测试遗漏点。<br />
|
||||
同时,切忌不要以开发代码的实现为依据设计测试用例。因为开发代码实现的错误会导致测试用例也出错,所以你应该根据原始需求设计测试用例。</p>
|
||||
</li>
|
||||
<li>
|
||||
**需要引入需求覆盖率和代码覆盖率来衡量测试执行的完备性,并以此为依据来找出遗漏的测试点。** 关于什么是需求覆盖率和代码覆盖率,我会在后续的文章中详细介绍。
|
||||
</li>
|
||||
|
||||
## 总结
|
||||
|
||||
最后,我来简单总结一下今天的主要内容。
|
||||
|
||||
首先,你需要明白,“好的”测试用例一定是一个完备的集合,它能够覆盖所有等价类以及各种边界值,而能否发现软件缺陷并不是衡量测试用例好坏的标准。
|
||||
|
||||
其次,设计测试用例的方法有很多种,但综合运用等价类划分、边界值分析和错误推测方法,可以满足绝大多数软件测试用例设计的需求。
|
||||
|
||||
再次,“好的”测试用例在设计时,需要从软件功能需求出发,全面地、无遗漏地识别出测试需求至关重要。
|
||||
|
||||
最后,如果想设计一个“好的”测试用例,你必须要深入理解被测软件的架构设计,深入软件内部的处理逻辑,需求覆盖率和代码覆盖率这两个指标可以帮你衡量测试执行的完备性。
|
||||
|
||||
## 思考题
|
||||
|
||||
在设计测试用例的过程中,你有哪些可供分享的好的实践和方法?
|
||||
|
||||
欢迎你给我留言。
|
||||
|
||||
|
||||
253
极客时间专栏/软件测试52讲/测试基础知识篇/03 | 什么是单元测试?如何做好单元测试?.md
Normal file
253
极客时间专栏/软件测试52讲/测试基础知识篇/03 | 什么是单元测试?如何做好单元测试?.md
Normal file
@@ -0,0 +1,253 @@
|
||||
<audio id="audio" title="03 | 什么是单元测试?如何做好单元测试?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/cb/fc/cbd4f1e7e38b83841954a7c0dfba93fc.mp3"></audio>
|
||||
|
||||
今天我要跟你分享的主题是单元测试,如果你没有开发背景,感觉这篇文章理解起来有难度,那你可以在学完后续的“代码级测试”系列的文章后,再回过头来看一遍这篇文章,相信你会有醍醐灌顶的感觉。
|
||||
|
||||
## 什么是单元测试?
|
||||
|
||||
在正式开始今天的话题之前,我先给你分享一个工厂生产电视机的例子。
|
||||
|
||||
工厂首先会将各种电子元器件按照图纸组装在一起构成各个功能电路板,比如供电板、音视频解码板、射频接收板等,然后再将这些电路板组装起来构成一个完整的电视机。
|
||||
|
||||
如果一切顺利,接通电源后,你就可以开始观看电视节目了。但是很不幸,大多数情况下组装完成的电视机根本无法开机,这时你就需要把电视机拆开,然后逐个模块排查问题。
|
||||
|
||||
假设你发现是供电板的供电电压不足,那你就要继续逐级排查组成供电板的各个电子元器件,最终你可能发现罪魁祸首是一个电容的故障。这时,为了定位到这个问题,你已经花费了大量的时间和精力。
|
||||
|
||||
那在后续的生产中,如何才能避免类似的问题呢?
|
||||
|
||||
你可能立即就会想到,为什么不在组装前,就先测试每个要用到的电子元器件呢?这样你就可以先排除有问题的元器件,最大程度地防止组装完成后逐级排查问题的事情发生。
|
||||
|
||||
实践也证明,这的确是一个行之有效的好办法。
|
||||
|
||||
如果把电视机的生产、测试和软件的开发、测试进行类比,你可以发现:
|
||||
|
||||
<li>
|
||||
电子元器件就像是软件中的单元,通常是函数或者类,对单个元器件的测试就像是软件测试中的单元测试;
|
||||
</li>
|
||||
<li>
|
||||
组装完成的功能电路板就像是软件中的模块,对电路板的测试就像是软件中的集成测试;
|
||||
</li>
|
||||
<li>
|
||||
电视机全部组装完成就像是软件完成了预发布版本,电视机全部组装完成后的开机测试就像是软件中的系统测试。
|
||||
</li>
|
||||
|
||||
通过这个类比,相信你已经体会到了单元测试对于软件整体质量的重要性,那么单元测试到底是什么呢?
|
||||
|
||||
>
|
||||
单元测试是指,对软件中的最小可测试单元在与程序其他部分相隔离的情况下进行检查和验证的工作,这里的最小可测试单元通常是指函数或者类。
|
||||
|
||||
|
||||
单元测试通常由开发工程师完成,一般会伴随开发代码一起递交至代码库。单元测试属于最严格的软件测试手段,是最接近代码底层实现的验证手段,可以在软件开发的早期以最小的成本保证局部代码的质量。
|
||||
|
||||
另外,单元测试都是以自动化的方式执行,所以在大量回归测试的场景下更能带来高收益。
|
||||
|
||||
同时,你还会发现,单元测试的实施过程还可以帮助开发工程师改善代码的设计与实现,并能在单元测试代码里提供函数的使用示例,因为单元测试的具体表现形式就是对函数以各种不同输入参数组合进行调用,这些调用方法构成了函数的使用说明。
|
||||
|
||||
## 如何做好单元测试?
|
||||
|
||||
要做好单元测试,你首先必须弄清楚单元测试的对象是代码,以及代码的基本特征和产生错误的原因,然后你必须掌握单元测试的基本方法和主要技术手段,比如什么是驱动代码、桩代码和Mock代码等。
|
||||
|
||||
**第一,代码的基本特征与产生错误的原因**
|
||||
|
||||
开发语言多种多样,程序实现的功能更是千变万化,我可以提炼出代码的基本特征,并总结出代码缺陷的主要原因么?答案是肯定,你静下心来思考时,会发现其中是有规律可寻的。
|
||||
|
||||
因为无论是开发语言还是脚本语言,都会有条件分支、循环处理和函数调用等最基本的逻辑控制,如果抛开代码需要实现的具体业务逻辑,仅看代码结构的话,你会发现所有的代码都是在对数据进行分类处理,每一次条件判定都是一次分类处理,嵌套的条件判定或者循环执行,也是在做分类处理。
|
||||
|
||||
如果有任何一个分类遗漏,都会产生缺陷;如果有任何一个分类错误,也会产生缺陷;如果分类正确也没有遗漏,但是分类时的处理逻辑错误,也同样会产生缺陷。
|
||||
|
||||
可见,**要做到代码功能逻辑正确,必须做到分类正确并且完备无遗漏,同时每个分类的处理逻辑必须正确。**
|
||||
|
||||
在具体的工程实践中,开发工程师为了设计并实现逻辑功能正确的代码,通常会有如下的考虑过程:
|
||||
|
||||
<li>
|
||||
如果要实现正确的功能逻辑,会有哪几种正常的输入;
|
||||
</li>
|
||||
<li>
|
||||
是否有需要特殊处理的多种边界输入;
|
||||
</li>
|
||||
<li>
|
||||
各种潜在非法输入的可能性以及如何处理。
|
||||
</li>
|
||||
|
||||
讲到这里,你有没有回想起我跟你分享的“等价类”。没错,这些开发工程师眼中的代码“功能点”,就是单元测试的“等价类”。
|
||||
|
||||
**第二,单元测试用例详解**
|
||||
|
||||
在实际工作中,你想做好单元测试,就必须对单元测试的用例设计有深入的理解。
|
||||
|
||||
通常来讲,**单元测试的用例是一个“输入数据”和“预计输出”的集合。** 你需要针对确定的输入,根据逻辑功能推算出预期正确的输出,并且以执行被测试代码的方式进行验证,用一句话概括就是“在明确了代码需要实现的逻辑功能的基础上,什么输入,应该产生什么输出”。
|
||||
|
||||
但是,对于单元测试来讲,测试用例的“输入数据”和“预计输出”可能远比你想得要复杂得多。
|
||||
|
||||
首先,让我来解释一下单元测试用例“输入数据”都有哪些种类,**如果你想当然的认为只有被测试函数的输入参数是“输入数据”的话,那就大错特错了。** 这里我总结了几种“输入数据”,希望可以帮助你理解什么才是完整的单元测试“输入数据”:
|
||||
|
||||
<li>
|
||||
被测试函数的输入参数;
|
||||
</li>
|
||||
<li>
|
||||
被测试函数内部需要读取的全局静态变量;
|
||||
</li>
|
||||
<li>
|
||||
被测试函数内部需要读取的成员变量;
|
||||
</li>
|
||||
<li>
|
||||
函数内部调用子函数获得的数据;
|
||||
</li>
|
||||
<li>
|
||||
函数内部调用子函数改写的数据;
|
||||
</li>
|
||||
<li>
|
||||
嵌入式系统中,在中断调用时改写的数据;
|
||||
</li>
|
||||
<li>
|
||||
…
|
||||
</li>
|
||||
|
||||
然后,让我们再来看看“预计输出”,**如果没有明确的预计输出,那么测试本身就失去了意义。同样地,“预计输出” 绝对不是只有函数返回值这么简单,还应该包括函数执行完成后所改写的所有数据。** 具体来看有以下几大类:
|
||||
|
||||
<li>
|
||||
被测试函数的返回值;
|
||||
</li>
|
||||
<li>
|
||||
被测试函数的输出参数;
|
||||
</li>
|
||||
<li>
|
||||
被测试函数所改写的成员变量;
|
||||
</li>
|
||||
<li>
|
||||
被测试函数所改写的全局变量;
|
||||
</li>
|
||||
<li>
|
||||
被测试函数中进行的文件更新;
|
||||
</li>
|
||||
<li>
|
||||
被测试函数中进行的数据库更新;
|
||||
</li>
|
||||
<li>
|
||||
被测试函数中进行的消息队列更新;
|
||||
</li>
|
||||
<li>
|
||||
…
|
||||
</li>
|
||||
|
||||
另外,对于预计输出值,你必须严格根据代码的功能逻辑来设定,而不能通过阅读代码来推算预期输出,否则就是“掩耳盗铃”了。
|
||||
|
||||
你不要觉得好笑,这种情况经常出现。主要原因是,开发工程师自己测试自己写的代码时会有严重的思维惯性,以至于会根据自己的代码实现来推算预计输出。
|
||||
|
||||
最后,我还要再提一个点,如果某些等价类或者边界值,开发工程师在开发的时候都没有考虑到,测试的时候就更不会去设计对应的测试用例了,这样也就会造成测试盲区。
|
||||
|
||||
**第三,驱动代码,桩代码和Mock代码**
|
||||
|
||||
驱动代码,桩代码和Mock代码,是单元测试中最常出现的三个名词。驱动代码是用来调用被测函数的,而桩代码和Mock代码是用来代替被测函数调用的真实代码的。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/4b/2f/4b593086d9370bea9afc2d12219a0c2f.png" alt="" /><br />
|
||||
驱动代码,桩代码和Mock代码三者的逻辑关系
|
||||
|
||||
**驱动代码(Driver)指调用被测函数的代码**,在单元测试过程中,驱动模块通常包括调用被测函数前的数据准备、调用被测函数以及验证相关结果三个步骤。驱动代码的结构,通常由单元测试的框架决定。
|
||||
|
||||
**桩代码(Stub)是用来代替真实代码的临时代码。** 比如,某个函数A的内部实现中调用了一个尚未实现的函数B,为了对函数A的逻辑进行测试,那么就需要模拟一个函数B,这个模拟的函数B的实现就是所谓的桩代码。
|
||||
|
||||
为了帮你理解,我带你看下这个例子:假定函数A是被测函数,其内部调用了函数B(具体伪代码如下):
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/4c/35/4cab48446a65adf0258c74a132cb3c35.png" alt="" /><br />
|
||||
被测函数A内部调用了函数B
|
||||
|
||||
在单元测试阶段,由于函数B尚未实现,但是为了不影响对函数A自身实现逻辑的测试,你可以用一个假的函数B来代替真实的函数B,那么这个假的函数B就是桩函数。
|
||||
|
||||
为了实现函数A的全路径覆盖,你需要控制不同的测试用例中函数B的返回值,那么桩函数B的伪代码就应该是这个样子的:
|
||||
|
||||
>
|
||||
当执行第一个测试用例的时候,桩函数B应该返回true,而当执行第二个测试用例的时候,桩函数B应该返回false。
|
||||
|
||||
|
||||
这样就覆盖了被测试函数A的if-else的两个分支。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a6/5b/a68bedf39621240b8e36fc503bfd815b.jpeg" alt="" />
|
||||
|
||||
桩函数内部实现
|
||||
|
||||
从这个例子可以看出,**桩代码的应用首先起到了隔离和补齐的作用,使被测代码能够独立编译、链接,并独立运行。同时,桩代码还具有控制被测函数执行路径的作用。**
|
||||
|
||||
所以,编写桩代码通常需要遵守以下三个原则:
|
||||
|
||||
<li>
|
||||
桩函数要具有与原函数完全相同的原形,仅仅是内部实现不同,这样测试代码才能正确链接到桩函数;
|
||||
</li>
|
||||
<li>
|
||||
用于实现隔离和补齐的桩函数比较简单,只需保持原函数的声明,加一个空的实现,目的是通过编译链接;
|
||||
</li>
|
||||
<li>
|
||||
实现控制功能的桩函数是应用最广泛的,要根据测试用例的需要,输出合适的数据作为被测函数的内部输入。
|
||||
</li>
|
||||
|
||||
Mock代码和桩代码非常类似,都是用来代替真实代码的临时代码,起到隔离和补齐的作用。但是很多人,甚至是具有多年单元测试经验的开发工程师,也很难说清这二者的区别。
|
||||
|
||||
在我看来,Mock代码和桩代码的本质区别是:测试期待结果的验证(Assert and Expectiation)。
|
||||
|
||||
<li>
|
||||
对于Mock代码来说,我们的关注点是Mock方法有没有被调用,以什么样的参数被调用,被调用的次数,以及多个Mock函数的先后调用顺序。所以,在使用Mock代码的测试中,对于结果的验证(也就是assert),通常出现在Mock函数中。
|
||||
</li>
|
||||
<li>
|
||||
对于桩代码来说,我们的关注点是利用Stub来控制被测函数的执行路径,不会去关注Stub是否被调用以及怎么样被调用。所以,你在使用Stub的测试中,对于结果的验证(也就是assert),通常出现在驱动代码中。
|
||||
</li>
|
||||
|
||||
在这里,我只想让你理解两者的本质区别以确保你知识结构的完整性,如果你想深入比较,可以参考马丁·福勒(Martin Fowler)的著名文章[《Mock代码不是桩代码》(Mocks Aren’t Stubs)](https://martinfowler.com/articles/mocksArentStubs.html)。
|
||||
|
||||
因为从实际应用的角度看,就算你不能分清Mock代码和桩代码,也不会影响你做好单元测试,所以我并没有从理论层面去深入比较它们的区别。
|
||||
|
||||
## 实际项目中如何开展单元测试?
|
||||
|
||||
最后我要跟你聊一下,实际软件项目中如何开展单元测试?
|
||||
|
||||
<li>
|
||||
并不是所有的代码都要进行单元测试,通常只有底层模块或者核心模块的测试中才会采用单元测试。
|
||||
</li>
|
||||
<li>
|
||||
<p>你需要确定单元测试框架的选型,这和开发语言直接相关。比如,Java最常用的单元测试框架是Junit和TestNG;C/C++最常用的单元测试框架是CppTest和Parasoft C/C++test;框架选型完成后,你还需要对桩代码框架和Mock代码框架选型,选型的主要依据是开发所采用的具体技术栈。<br />
|
||||
通常,单元测试框架、桩代码/Mock代码的选型工作由开发架构师和测试架构师共同决定。</p>
|
||||
</li>
|
||||
<li>
|
||||
为了能够衡量单元测试的代码覆盖率,通常你还需要引入计算代码覆盖率的工具。不同的语言会有不同的代码覆盖率统计工具,比如Java的JaCoCo,JavaScript的Istanbul。在后续的文章中,我还会详细为你介绍代码覆盖率的内容。
|
||||
</li>
|
||||
<li>
|
||||
最后你需要把单元测试执行、代码覆盖率统计和持续集成流水线做集成,以确保每次代码递交,都会自动触发单元测试,并在单元测试执行过程中自动统计代码覆盖率,最后以“单元测试通过率”和“代码覆盖率”为标准来决定本次代码递交是否能够被接受。
|
||||
</li>
|
||||
|
||||
如果你有开发背景,那么入门单元测试是比较容易的。但真正在项目中全面推行单元测试时,你会发现还有一些困难需要克服:
|
||||
|
||||
<li>
|
||||
紧密耦合的代码难以隔离;
|
||||
</li>
|
||||
<li>
|
||||
隔离后编译链接运行困难;
|
||||
</li>
|
||||
<li>
|
||||
代码本身的可测试性较差,通常代码的可测试性和代码规模成正比;
|
||||
</li>
|
||||
<li>
|
||||
无法通过桩代码直接模拟系统底层函数的调用;
|
||||
</li>
|
||||
<li>
|
||||
代码覆盖率越往后越难提高。
|
||||
</li>
|
||||
|
||||
## 总结
|
||||
|
||||
我给你详细介绍了单元测试的概念,和你重点讨论了用例的组成,以及在实际项目中开展单元测试的方法,你需要注意以下三个问题:
|
||||
|
||||
<li>
|
||||
代码要做到功能逻辑正确,必须做到分类正确并且完备无遗漏,同时每个分类的处理逻辑必须正确;
|
||||
</li>
|
||||
<li>
|
||||
单元测试是对软件中的最小可测试单元在与软件其他部分相隔离的情况下进行的代码级测试;
|
||||
</li>
|
||||
<li>
|
||||
桩代码起到了隔离和补齐的作用,使被测代码能够独立编译、链接,并运行。
|
||||
</li>
|
||||
|
||||
## 思考题
|
||||
|
||||
你所在的公司有做单元测试吗?实施单元测试过程中遇到过哪些问题,你是如何解决的?
|
||||
|
||||
欢迎你给我留言。
|
||||
|
||||
|
||||
137
极客时间专栏/软件测试52讲/测试基础知识篇/04 | 为什么要做自动化测试?什么样的项目适合做自动化测试?.md
Normal file
137
极客时间专栏/软件测试52讲/测试基础知识篇/04 | 为什么要做自动化测试?什么样的项目适合做自动化测试?.md
Normal file
@@ -0,0 +1,137 @@
|
||||
<audio id="audio" title="04 | 为什么要做自动化测试?什么样的项目适合做自动化测试?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/09/06/09383c2f4a9f4226e7378bdf7488ce06.mp3"></audio>
|
||||
|
||||
在上一篇文章中,我为你介绍了什么是单元测试,以及如何做好单元测试,今天我来跟你聊聊什么是自动化测试,为什么要做自动化测试,以及什么样的项目适合做自动化测试。
|
||||
|
||||
## 什么是自动化测试?
|
||||
|
||||
不管你是刚入行的小白,还是已经在做软件测试的工作,相信你一定听说过或者接触过自动化测试。那么,自动化测试到底是什么意思呢?
|
||||
|
||||
顾名思义,**自动化测试是,把人对软件的测试行为转化为由机器执行测试行为的一种实践**,对于最常见的GUI自动化测试来讲,就是由自动化测试工具模拟之前需要人工在软件界面上的各种操作,并且自动验证其结果是否符合预期。
|
||||
|
||||
你是不是有点小激动?这似乎开启了用机器代替重复手工劳动的自动化时代,你可以从简单重复劳动中解放出来了。但现实呢?
|
||||
|
||||
**自动化测试的本质是先写一段代码,然后去测试另一段代码,所以实现自动化测试用例本身属于开发工作,需要投入大量的时间和精力,并且已经开发完成的用例还必须随着被测对象的改变而不断更新,你还需要为此付出维护测试用例的成本。**
|
||||
|
||||
当你发现自动化测试用例的维护成本高于其节省的测试成本时,自动化测试就失去了价值与意义,你也就需要在是否使用自动化测试上权衡取舍了。
|
||||
|
||||
## 为什么需要自动化测试?
|
||||
|
||||
为了让你更好地理解自动化测试的价值,即为什么需要自动化测试,我先来跟你聊聊自动化测试通常有哪些优势:
|
||||
|
||||
<li>
|
||||
自动化测试可以替代大量的手工机械重复性操作,测试工程师可以把更多的时间花在更全面的用例设计和新功能的测试上;
|
||||
</li>
|
||||
<li>
|
||||
自动化测试可以大幅提升回归测试的效率,非常适合敏捷开发过程;
|
||||
</li>
|
||||
<li>
|
||||
自动化测试可以更好地利用无人值守时间,去更频繁地执行测试,特别适合现在非工作时间执行测试,工作时间分析失败用例的工作模式;
|
||||
</li>
|
||||
<li>
|
||||
自动化测试可以高效实现某些手工测试无法完成或者代价巨大的测试类型,比如关键业务7×24小时持续运行的系统稳定性测试和高并发场景的压力测试等;
|
||||
</li>
|
||||
<li>
|
||||
自动化测试还可以保证每次测试执行的操作以及验证的一致性和可重复性,避免人为的遗漏或疏忽。
|
||||
</li>
|
||||
|
||||
而为了避免对自动化测试的过度依赖,你还需要了解自动化测试有哪些劣势,这将帮你绕过实际工作中的“坑”。
|
||||
|
||||
<li>
|
||||
自动化测试并不能取代手工测试,它只能替代手工测试中执行频率高、机械化的重复步骤。你千万不要奢望所有的测试都自动化,否则一定会得不偿失。
|
||||
</li>
|
||||
<li>
|
||||
<p>自动测试远比手动测试脆弱,无法应对被测系统的变化,业界一直有句玩笑话“开发手一抖,自动化测试忙一宿”,这也从侧面反映了自动化测试用例的维护成本一直居高不下的事实。<br />
|
||||
其根本原因在于自动化测试本身不具有任何“智能”,只是按部就班地执行事先定义好的测试步骤并验证测试结果。对于执行过程中出现的明显错误和意外事件,自动化测试没有任何处理能力。</p>
|
||||
</li>
|
||||
<li>
|
||||
自动化测试用例的开发工作量远大于单次的手工测试,所以只有当开发完成的测试用例的有效执行次数大于等于5次时,才能收回自动化测试的成本。
|
||||
</li>
|
||||
<li>
|
||||
手工测试发现的缺陷数量通常比自动化测试要更多,并且自动化测试仅仅能发现回归测试范围的缺陷。
|
||||
</li>
|
||||
<li>
|
||||
测试的效率很大程度上依赖自动化测试用例的设计以及实现质量,不稳定的自动化测试用例实现比没有自动化更糟糕。
|
||||
</li>
|
||||
<li>
|
||||
实行自动化测试的初期,用例开发效率通常都很低,大量初期开发的用例通常会在整个自动化测试体系成熟,和测试工程师全面掌握测试工具后,需要重构。
|
||||
</li>
|
||||
<li>
|
||||
业务测试专家和自动化测试专家通常是两批人,前者懂业务不懂自动化技术,后者懂自动化技术但不懂业务,只有二者紧密合作,才能高效开展自动化测试。
|
||||
</li>
|
||||
<li>
|
||||
自动化测试开发人员必须具备一定的编程能力,这对传统的手工测试工程师会是一个挑战。
|
||||
</li>
|
||||
|
||||
##
|
||||
|
||||
什么样的项目适合自动化测试?
|
||||
|
||||
看到这里,你心里可能在暗自嘀咕,“有没有搞错啊,自动化测试的劣势居然比优势还多”。那为什么还有那么多的企业级项目在实行自动化测试呢?那么,我接下来要讲的内容就是,到底什么样的项目适合自动化测试?
|
||||
|
||||
**第一,需求稳定,不会频繁变更。**
|
||||
|
||||
自动化测试最怕的就是需求不稳定,过高的需求变更频率会导致自动化测试用例的维护成本直线上升。刚刚开发完成并调试通过的用例可能因为界面变化,或者是业务流程变化,不得不重新开发调试。所以自动化测试更适用于需求相对稳定的软件项目。
|
||||
|
||||
**第二,研发和维护周期长,需要频繁执行回归测试。**
|
||||
|
||||
**1. 在我看来,软件产品比软件项目更适合做自动化测试。**
|
||||
|
||||
首先,软件产品的生命周期一般都比较长,通常会有多个版本陆续发布,每次版本发布都会有大量的回归测试需求。
|
||||
|
||||
同时,软件产品预留给自动化测试开发的时间也比较充裕,可以和产品一起迭代。
|
||||
|
||||
其次,自动化测试用例的执行比高于1:5,即开发完成的用例至少可以被有效执行5次以上时,自动化测试的优势才可以被更好地体现。
|
||||
|
||||
**2. 对于软件项目的自动化测试,就要看项目的具体情况了。**
|
||||
|
||||
如果短期的一次性项目,就算从技术上讲自动化测试的可行性很高,但从投入产出比(ROI)的角度看并不建议实施自动化,因为千辛万苦开发完成的自动化用例可能执行一两次,项目就结束了。我还遇到过更夸张的情况,自动化测试用例还没开发完,项目都已经要上线了。
|
||||
|
||||
所以,对于这种短期的一次性项目,我觉得你应该选择手工探索式测试,以发现缺陷为第一要务。而对于一些中长期项目,我的建议是:对比较稳定的软件功能进行自动化测试,对变动较大或者需求暂时不明确的功能进行手工测试,最终目标是用20%的精力去覆盖80%的回归测试。
|
||||
|
||||
**第三,需要在多种平台上重复运行相同测试的场景。**
|
||||
|
||||
这样的场景其实有很多,比如:
|
||||
|
||||
- 对于GUI测试,同样的测试用例需要在多种不同的浏览器上执行;
|
||||
- 对于移动端应用测试,同样的测试用例需要在多个不同的Android或者iOS版本上执行,或者是同样的测试需要在大量不同的移动终端上执行;
|
||||
- 对于一些企业级软件,如果对于不同的客户有不同的定制版本,各个定制版本的主体功能绝大多数是一致的,可能只有个别功能有轻微差别,测试也是需要覆盖每个定制版本的所有测试;
|
||||
- ……
|
||||
|
||||
这些都是自动化测试的最佳应用场景,因为单个测试用例都需要被反复执行多次,能够使自动化测试的投资回报率最大化。
|
||||
|
||||
**第四,某些测试项目通过手工测试无法实现,或者手工成本太高。**
|
||||
|
||||
对于所有的性能和压力测试,很难通过手工方式实现。
|
||||
|
||||
比如,某一个项目要求进行一万并发用户的基准性能测试(Benchmark test),难道你真的要找一万个用户按照你的口令来操作被测软件吗?又比如,对于7×24小时的稳定性测试,难道你也要找一批用户没日没夜地操作被测软件吗?
|
||||
|
||||
这个时候,你就必须借助自动化测试技术了,用机器来模拟大量用户反复操作被测软件的场景。当然对于此类测试是不可能通过GUI操作来模拟大量用户行为的,你必须基于协议的自动化测试技术,这个我会在后续的性能测试章节详细叙述。
|
||||
|
||||
**第五,被测软件的开发较为规范,能够保证系统的可测试性。**
|
||||
|
||||
从技术上讲,如果要实现稳定的自动化测试,被测软件的开发过程就必须规范。比如,GUI上的控件命名如果没有任何规则可寻,就会造成GUI自动化的控件识别与定位不稳定,从而影响自动化测试的效率。
|
||||
|
||||
**另外,某些用例的自动化必须要求开发人员在产品中预留可测试性接口,否则后续的自动化会很难开展。**
|
||||
|
||||
比如,有些用户登录操作,需要图片验证码,如果开发人员没有提供绕开图片验证码的路径,那么自动化测试就必须借助光学字符识别(OCR)技术来对图片验证码进行模式识别,而它的设计初衷是为了防止机器人操作,可想而知OCR的识别率会很低,就会直接影响用例的稳定性。
|
||||
|
||||
**第六,测试人员已经具备一定的编程能力。**
|
||||
|
||||
如果测试团队的成员没有任何开发编程的基础,那你想要推行自动化测试就会有比较大的阻力。这个阻力会来自于两个方面:
|
||||
|
||||
- 前期的学习成本通常会比较大,很难在短期内对实际项目产生实质性的帮助,此时如果管理层对自动化测试没有正确的预期,很可能会叫停自动化测试;
|
||||
- 测试工程师通常会非常热衷于学习使用自动化测试技术,以至于他们的工作重点会发生错误的偏移,把大量的精力放在自动化测试技术的学习与实践上,而忽略了测试用例的设计,这将直接降低软件整体的质量。
|
||||
|
||||
## 总结
|
||||
|
||||
自动化测试是,把人工对软件的测试转化为由机器执行测试行为的一种实践,可以把测试工程师从机械重复的测试工作中解脱出来,将更多的精力放在新功能的测试和更全面的测试用例设计上。
|
||||
|
||||
然而自动化测试试一把“双刃剑”,虽然它可以从一定程度上解放测试工程师的劳动力,完成一些人工无法实现的测试,但并不适用于所有的测试场景,如果维护自动化测试的代价高过了节省的测试成本,那么在这样的项目中推进自动化测试就会得不偿失。
|
||||
|
||||
## 思考题
|
||||
|
||||
你在实际项目中接触过哪些自动化测试,自动化测试用例的执行比通常是多少?如果执行比过低,需要频繁更新测试用例,那你思考过你的项目是否真的适合自动化测试吗?或者说,这个项目的哪些部分更适合实施自动化测试?
|
||||
|
||||
欢迎你给我留言。
|
||||
|
||||
|
||||
148
极客时间专栏/软件测试52讲/测试基础知识篇/05 | 你知道软件开发各阶段都有哪些自动化测试技术吗?.md
Normal file
148
极客时间专栏/软件测试52讲/测试基础知识篇/05 | 你知道软件开发各阶段都有哪些自动化测试技术吗?.md
Normal file
@@ -0,0 +1,148 @@
|
||||
<audio id="audio" title="05 | 你知道软件开发各阶段都有哪些自动化测试技术吗?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/2a/f3/2a446f3f337a32b57516011364d592f3.mp3"></audio>
|
||||
|
||||
在前面的文章中,我介绍了为什么要做自动化测试,以及什么样的项目适合做自动化测试,那么现在我来说说软件开发生命周期的各个阶段都有哪些类型的自动化测试技术。
|
||||
|
||||
说到自动化测试,你可能最为熟悉的就是GUI自动化测试了。比如,早年的C/S架构,通常就是用自动化测试脚本打开被测应用,然后在界面上以自动化的方式执行一系列的操作;再比如,现今的Web站点测试,也是用自动化测试脚本打开浏览器,然后输入要访问的网址,之后用自动化脚本识别定位页面元素,并进行相应的操作。
|
||||
|
||||
因此,说到自动化测试时,你的第一反应很可能就是GUI自动化测试。然而,**在软件研发生命周期的各个阶段都有自动化测试技术的存在,并且对提升测试效率有着至关重要的作用。**
|
||||
|
||||
今天这篇文章,我将会以不同的软件开发阶段涉及的自动化测试技术为主线,带你了解单元测试、代码级集成测试、Web Service测试和GUI测试阶段的自动化技术,希望可以帮助你更深入地理解“自动化测试”的内涵以及外延。
|
||||
|
||||
## 单元测试的自动化技术
|
||||
|
||||
首先,你可能认为单元测试本身就是自动化的,因为它根据软件详细设计采用等价类划分和边界值分析方法设计测试用例,在测试代码实现后再以自动化的方式统一执行。
|
||||
|
||||
这个观点非常正确,但这仅仅是一部分,并没有完整地描述单元测试“自动化”的内涵。**从广义上讲,单元测试阶段的“自动化”内涵不仅仅指测试用例执行的自动化,还应该包含以下五个方面:**
|
||||
|
||||
1. 用例框架代码生成的自动化;
|
||||
1. 部分测试输入数据的自动化生成;
|
||||
1. 自动桩代码的生成;
|
||||
1. 被测代码的自动化静态分析;
|
||||
1. 测试覆盖率的自动统计与分析。
|
||||
|
||||
你可能感觉这些内容有些陌生,不过没关系,下面我就详细地跟你说说每一条的具体含义。
|
||||
|
||||
**第一,用例框架代码生成的自动化**
|
||||
|
||||
有些框架代码应该由自动化工具生成,而不是由开发者手工完成。这样一来,单元测试开发者可以把更多的精力放在测试逻辑的覆盖和测试数据的选择上,从而大幅提高单元测试用例的质量和开发效率。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/48/4a/4824e7700dfc20db4808557d254d4a4a.png" alt="" /><br />
|
||||
TestNG框架代码应该由自动化工具生成
|
||||
|
||||
**第二,部分测试输入数据的自动化生成**
|
||||
|
||||
这部分是指,自动化工具能够根据不同变量类型自动生成测试输入数据。自动化工具本身不可能明白代码逻辑,你可能很难理解它是如何根据需要测试的代码逻辑生成合适的输入数据,并且去判断预计的测试结果的。那我给你举个例子,你就很容易明白了。
|
||||
|
||||
比如,某个被测函数的原型是void fun(int* p, short b),那么测试数据自动生成技术就会为输入参数int* p自动生成“空”和“非空”的两个指针p,然后分别执行函数void fun(int* p, short b),并观察函数的执行情况。
|
||||
|
||||
如果函数内部没有对空指针进行特殊处理,那么函数fun的调用必定会抛出异常,从而发现函数的设计缺陷。同样地,对于输入参数short b会自动生成超出short范围的b,测试函数fun的行为。
|
||||
|
||||
**第三,自动桩代码的生成**
|
||||
|
||||
**简单地说,桩代码(stub code)是用来代替真实代码的临时代码。** 比如,某个函数A的内部实现中调用了一个尚未实现的函数B,为了对函数A的逻辑进行测试,那么就需要模拟一个函数B,这个模拟的函数B实现就是所谓的桩代码。
|
||||
|
||||
自动桩代码的生成是指自动化工具可以对被测试代码进行扫描分析,自动为被测函数内部调用的其他函数生成可编程的桩代码,并提供基于测试用例的桩代码管理机制。此时,**单元测试开发者只需重点关注桩代码内的具体逻辑实现,以及桩代码的返回值。**
|
||||
|
||||
必要的时候,自动化工具还需要实现 “抽桩”,以适应后续的代码级集成测试的需求。
|
||||
|
||||
那什么是“抽桩”呢?其实也很简单,在单元测试阶段,假如函数A内部调用的函数B是桩代码,那么在代码级集成测试阶段,我们希望函数A不再调用假的函数B,而是调用真实的函数B,这个用真实函数B代替原本桩代码函数B的操作,就称为“抽桩”。
|
||||
|
||||
**第四,被测代码的自动化静态分析**
|
||||
|
||||
静态分析主要指代码的静态扫描,目的是识别出违反编码规则或编码风格的代码行。通常这部分工作是结合项目具体的编码规则和编码风格,由自动化工具通过内建规则和用户自定义规则自动化完成的。目前比较常用的代码静态分析工具有Sonar和Coverity等。
|
||||
|
||||
严格意义上讲,静态分析不属于单元测试的范畴,但这部分工作一般是在单元测试阶段通过自动化工具完成的,所以我也把它归入到了单元测试自动化的范畴。
|
||||
|
||||
**第五,测试覆盖率的自动统计与分析**
|
||||
|
||||
单元测试用例执行结束后,自动化工具可以自动统计各种测试覆盖率,包括代码行覆盖率、分支覆盖率、MC/DC覆盖率等。这些自动统计的指标,可以帮你衡量单元测试用例集合的充分性和完备性,并可以为你提供适当增补测试用例以提高测试覆盖率的依据。
|
||||
|
||||
## 代码级集成测试的自动化技术
|
||||
|
||||
通俗地讲,代码级集成测试是指将已经开发完成的软件模块放在一起测试。
|
||||
|
||||
从测试用例设计和测试代码结构来看,代码级集成测试和单元测试非常相似,它们都是对被测试函数以不同的输入参数组合进行调用并验证结果,只不过代码级集成测试的关注点,更多的是软件模块之间的接口调用和数据传递。
|
||||
|
||||
代码级集成测试与单元测试最大的区别只是,代码级集成测试中被测函数内部调用的其他函数必须是真实的,不允许使用桩代码代替,而单元测试中允许使用桩代码来模拟内部调用的其他函数。
|
||||
|
||||
以上的这些异同点就决定了代码级集成测试“自动化”的内涵与单元测试非常相似,尤其是在实际操作层面,比如测试用例的设计方法、测试用例的代码结构以及数据驱动思想的应用等等。
|
||||
|
||||
但是,代码级集成测试对测试框架的要求非常高,这个框架除了可以顺利装载自己的软件模块外,还必须能装载其他相互依赖的模块,做到被测软件模块可运行(Runnable)。
|
||||
|
||||
由于代码级集成测试主要应用在早期非互联网的传统软件企业,那时候的软件以“单体”应用居多,一个软件内部包含大量的功能,每一个软件功能都是通过不同的内部模块来实现的,那么这些内部模块在做集成的时候,就需要做代码级集成测试。
|
||||
|
||||
现在的开发理念追求的是系统复杂性的解耦,会去尽量避免“大单体”应用,采用Web Service或者RPC调用的方式来协作完成各个软件功能。所以现在的软件企业,尤其是互联网企业,基本不会去做代码级集成测试,我在这里也就不再进一步展开了。
|
||||
|
||||
## Web Service测试的自动化技术
|
||||
|
||||
**Web Service测试,主要是指SOAP API和REST API这两类API测试,最典型的是采用SoapUI或Postman等类似的工具。但这类测试工具基本都是界面操作手动发起Request并验证Response,所以难以和CI/CD集成,于是就出现了API自动化测试框架。**
|
||||
|
||||
如果采用API自动化测试框架来开发测试用例,那么这些测试用例的表现形式就是代码。为了让你更直观地理解基于代码的API测试用例是什么样子的,我给你举一个“创建用户”API的例子,你只需要看代码的大致步骤就可以了,具体到每行代码的含义,我会在后续文章中详细讲解。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/36/06/3679b5816177f9a5624f4fe737850d06.png" alt="" /><br />
|
||||
基于API自动化测试框架的测试用例示例(测试CreateUser API)
|
||||
|
||||
**对于基于代码的API测试用例,通常包含三大步骤:**
|
||||
|
||||
1. 准备API调用时需要的测试数据;
|
||||
1. 准备API的调用参数并发起API的调用;
|
||||
1. 验证API调用的返回结果。
|
||||
|
||||
目前最流行的API自动测试框架是REST Assured,它可以方便地发起Restful API调用并验证返回结果。关于REST Assured的用法和优点,我会在后续文章中详细介绍。
|
||||
|
||||
同样地,Web Service测试“自动化”的内涵不仅仅包括API测试用例执行的自动化,还包括以下四个方面:
|
||||
|
||||
1. 测试脚手架代码的自动化生成;
|
||||
1. 部分测试输入数据的自动生成;
|
||||
1. Response验证的自动化;
|
||||
1. 基于SoapUI或者Postman的自动化脚本生成。
|
||||
|
||||
接下来,我会依次为你解释这4个方面代表什么含义。
|
||||
|
||||
**第一,测试脚手架代码的自动化生成**<br />
|
||||
和单元测试阶段的用例框架代码自动生成一个道理,你在开发API测试的过程中更关心的是,如何设计测试用例的输入参数以及组合,以及在不同参数组合情况下Response的验证,而你不希望将精力浪费在代码层面如何组织测试用例、测试数据驱动如何实现等非测试业务上。
|
||||
|
||||
这时,测试脚手架代码的自动生成技术就派上用场了。它生成的测试脚手架代码,通常包含了被测试API的调用、测试数据与脚本的分离,以及Response验证的空实现。
|
||||
|
||||
**第二,部分测试输入数据的自动生成**
|
||||
|
||||
这一点和单元测试的测试输入数据的自动化生成也很类似,唯一不同的是,单元测试针对的参数是函数输入参数和函数内部输入,而API测试对应的是API的参数以及API调用的Payload。数据生成的原则同样遵循边界值原则。
|
||||
|
||||
**第三,Response验证的自动化**
|
||||
|
||||
对于API调用返回结果的验证,通常关注的点是返回状态码(status code)、Scheme结构以及具体的字段值。如果你写过这种类型的测试用例,那你就会知道字段值的验证相当麻烦,只有那些你明确写了assert的字段才会被验证,但是通常你不可能针对所有的字段都写assert,这时就需要Response验证的自动化技术了。
|
||||
|
||||
**Response验证自动化的核心思想是自动比较两次相同API调用的返回结果,并自动识别出有差异的字段值,比较过程可以通过规则配置去掉诸如时间戳、会话ID(Session ID)等动态值。** 这部分内容,我会在后续文章中详细讲解。
|
||||
|
||||
**第四,基于SoapUI或者Postman的自动化脚本生成**
|
||||
|
||||
你在使用SoapUI或者Postman等工具进行Web Service测试时,已经在这些工具里面积累了很多测试用例。那么,在引入了基于代码实现的API测试框架之后,就意味着需要把这些测试用例都用代码的方式重写一遍,而这额外的工作量是很难被接受的。
|
||||
|
||||
我的建议是,开发一个自动化代码转换生成工具。这个工具的输入是SoapUI或者Postman的测试用例元数据(即测试用例的JSON元文件),输出是符合API测试框架规范的基于代码实现的测试用例。这样一来,原本的测试用例积累可以直接转换成在CI/CD上可以直接接入的自动化测试用例。
|
||||
|
||||
对于新的测试用例,还可以继续用SoapUI或者Postman做初步的测试验证,初步验证没有问题后,直接转换成符合API测试框架规范的测试用例。对于复杂的测试用例,也可以直接基于代码来实现,而且灵活性会更好。
|
||||
|
||||
## GUI测试的自动化技术
|
||||
|
||||
GUI测试的自动化技术可能是你最熟悉的,也是发展时间最长、应用最广的自动化测试技术。它的核心思想是,基于页面元素识别技术,对页面元素进行自动化操作,以模拟实际终端用户的行为并验证软件功能的正确性。
|
||||
|
||||
目前,GUI自动化测试主要分为两大方向,传统Web浏览器和移动端原生应用(Native App)的GUI自动化。虽然二者采用的具体技术差别很大,但是用例设计的思路类似。
|
||||
|
||||
- 对于传统Web浏览器的GUI自动化测试,业内主流的开源方案采用Selenium,商业方案采用Micro Focus的UFT(前身是HP的QTP);
|
||||
- 对于移动端原生应用,通常采用主流的Appium,它对iOS环境集成了XCUITest,对Android环境集成了UIAutomator和Espresso。
|
||||
|
||||
这部分内容,我会在后续的文章中详细展开。
|
||||
|
||||
## 总结
|
||||
|
||||
我给你梳理了软件研发生命周期各个阶段的自动化测试技术,包括单元测试、代码级集成测试、Web Service测试和GUI测试的自动化技术,并给你归纳了每一类技术的核心方法和应用场景。
|
||||
|
||||
我希望你通过这篇文章,可以先对自动化测试的全局有一个比较清晰的认识,然后在后续的文章中我还会针对这些技术展开讨论,并给你分享一些相应的实际案例。
|
||||
|
||||
## 思考题
|
||||
|
||||
你现在所在的公司,是否实行代码级测试,用到了哪些自动化测试技术?
|
||||
|
||||
欢迎你给我留言。
|
||||
|
||||
|
||||
134
极客时间专栏/软件测试52讲/测试基础知识篇/06 | 你真的懂测试覆盖率吗?.md
Normal file
134
极客时间专栏/软件测试52讲/测试基础知识篇/06 | 你真的懂测试覆盖率吗?.md
Normal file
@@ -0,0 +1,134 @@
|
||||
<audio id="audio" title="06 | 你真的懂测试覆盖率吗?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/f5/b5/f510ae66b60a99ddd141ae424cb375b5.mp3"></audio>
|
||||
|
||||
在上一篇文章中,我为你介绍了软件测试各个阶段的自动化技术,在前面的文章中我也提到了测试覆盖率的概念,你当时可能有点不明白,那么今天我就和你详细聊聊测试覆盖率这个主题。
|
||||
|
||||
测试覆盖率通常被用来衡量测试的充分性和完整性,从广义的角度来讲,测试覆盖率主要分为两大类,一类是面向项目的需求覆盖率,另一类是更偏向技术的代码覆盖率。
|
||||
|
||||
## 需求覆盖率
|
||||
|
||||
**需求覆盖率是指测试对需求的覆盖程度,通常的做法是将每一条分解后的软件需求和对应的测试建立一对多的映射关系,最终目标是保证测试可以覆盖每个需求,以保证软件产品的质量。**
|
||||
|
||||
我们通常采用ALM,Doors和TestLink等需求管理工具来建立需求和测试的对应关系,并以此计算测试覆盖率。
|
||||
|
||||
需求覆盖率统计方法属于传统瀑布模型下的软件工程实践,传统瀑布模型追求自上而下地制定计划、分析需求、设计软件、编写代码、测试和运维等,在流程上是重量级的,已经很难适应当今互联网时代下的敏捷开发实践。
|
||||
|
||||
所以,互联网测试项目中很少直接基于需求来衡量测试覆盖率,而是将软件需求转换成测试需求,然后基于测试需求再来设计测试点。
|
||||
|
||||
因此,现在人们口中的测试覆盖率,通常默认指代码覆盖率,而不是需求覆盖率。
|
||||
|
||||
## 代码覆盖率
|
||||
|
||||
**简单来说,代码覆盖率是指,至少被执行了一次的条目数占整个条目数的百分比。**
|
||||
|
||||
如果“条目数”是语句,对应的就是代码行覆盖率;如果“条目数”是函数,对应的就是函数覆盖率;如果“条目数”是路径,那么对应的就是路径覆盖率。依此类推,你就可以得到绝大多数常见的代码覆盖率类型的定义。
|
||||
|
||||
这里我给你简单介绍一下最常用的三种代码覆盖率指标。
|
||||
|
||||
- 行覆盖率又称为语句覆盖率,指已经被执行到的语句占总可执行语句(不包含类似C++的头文件声明、代码注释、空行等等)的百分比。这是最常用也是要求最低的覆盖率指标。实际项目中通常会结合判定覆盖率或者条件覆盖率一起使用。
|
||||
- 判定覆盖又称分支覆盖,用以度量程序中每一个判定的分支是否都被测试到了,即代码中每个判断的取真分支和取假分支是否各被覆盖至少各一次。比如,对于if(a>0 && b>0),就要求覆盖“a>0 && b>0”为TURE和FALSE各一次。
|
||||
- 条件覆盖是指,判定中的每个条件的可能取值至少满足一次,度量判定中的每个条件的结果TRUE和FALSE是否都被测试到了。比如,对于if(a>0 && b>0),就要求“a>0”取TRUE和FALSE各一次,同时要求“b>0”取TRUE和FALSE各一次。
|
||||
|
||||
## 代码覆盖率的价值
|
||||
|
||||
**现在很多项目都在单元测试以及集成测试阶段统计代码覆盖率,但是我想说的是,统计代码覆盖率仅仅是手段,你必须透过现象看到事物的本质,才能从根本上保证软件整体的质量。**
|
||||
|
||||
统计代码覆盖率的根本目的是找出潜在的遗漏测试用例,并有针对性的进行补充,同时还可以识别出代码中那些由于需求变更等原因造成的不可达的废弃代码。
|
||||
|
||||
通常我们希望代码覆盖率越高越好,代码覆盖率越高越能说明你的测试用例设计是充分且完备的,但你也会发现测试的成本会随着代码覆盖率的提高以类似指数级的方式迅速增加。
|
||||
|
||||
如果想达到70%的代码覆盖率,你可能只需要30分钟的时间成本。但如果你想把代码覆盖率提高到90%,那么为了这额外的20%,你可能花的时间就远不止30分钟了。更进一步,你如果想达到100%的代码覆盖率,可想而知你花费的代价就会更大了。
|
||||
|
||||
那么,为什么代码覆盖率的提高,需要付出越来越大的代价呢?因为在后期,你需要大量的桩代码、Mock代码和全局变量的配合来控制执行路径。
|
||||
|
||||
所以,在软件企业中,只有单元测试阶段对代码覆盖率有较高的要求。因为从技术实现上讲,单元测试可以最大化地利用打桩技术来提高覆盖率。而你如果想在集成测试或者是GUI测试阶段将代码覆盖率提高到一定量级,那你所要付出的代价是巨大的,而且在很多情况下根本就实现不了。
|
||||
|
||||
## 代码覆盖率的局限性
|
||||
|
||||
我先来问你一个问题,如果你通过努力,已经把某个函数的MC/DC代码覆盖率(MC/DC覆盖率是最高标准的代码覆盖率指标,除了直接关系人生命安全的软件以外,很少会有项目会有严格的MC/DC覆盖率要求)做到了100%,软件质量是否就真的高枕无忧、万无一失了呢?
|
||||
|
||||
很不幸,即使你所设计的测试用例已经达到100%的代码覆盖率,软件产品的质量也做不到万无一失。其根本原因在于代码覆盖率的计算是基于现有代码的,并不能发现那些“未考虑某些输入”以及“未处理某些情况”形成的缺陷。
|
||||
|
||||
我给你举个极端的例子,如果一个被测函数里面只有一行代码,只要这个函数被调用过了,那么衡量这一行代码质量的所有覆盖率指标都会是100%,但是这个函数是否真正实现了应该需要实现的功能呢?
|
||||
|
||||
显然,代码覆盖率反映的仅仅是已有代码的哪些逻辑被执行过了,哪些逻辑还没有被执行过。以此为依据,你可以补充测试用例,可以去测试那些还没有覆盖到的执行路径。但也是仅此而已,对于那些压根还没有代码实现的部分,基于代码覆盖率的统计指标就无能为力了。
|
||||
|
||||
总结来讲,高的代码覆盖率不一定能保证软件的质量,但是低的代码覆盖率一定不能能保证软件的质量。
|
||||
|
||||
好了,现在你已经了解了代码覆盖率的概念、价值和局限性,那么接下来,我就以Java代码覆盖率工具为例,给你解释一下代码覆盖率工具的内部实现原理以及一些关键技术。
|
||||
|
||||
当你理解了这部分内容,以后再面对各个不同开发语言的不同代码覆盖率工具时,就可以做到胸有成竹地根据具体的项目性质,选择最合适的代码覆盖率工具了。
|
||||
|
||||
## 代码覆盖率工具
|
||||
|
||||
JaCoCo是一款Java代码的主流开源覆盖率工具,可以很方便地嵌入到Ant、Maven中,并且和很多主流的持续集成工具以及代码静态检查工具,比如Jekins和Sonar等,都有很好的集成。
|
||||
|
||||
首先,我先带你看看JaCoCo的代码覆盖率报告长什么样子。
|
||||
|
||||
如图1所示为JaCoCo的整体代码覆盖率统计报告,包括了每个Java代码文件的行覆盖率以及分支覆盖率统计,并给出了每个Java代码文件的行数、方法数和类数等具体信息。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/bf/6c/bfc0cf6bed338bc8bafc008b7fa1db6c.png" alt="" /><br />
|
||||
**图1 JaCoCo代码覆盖率统计报告实例**
|
||||
|
||||
如图2所示为每个Java文件内部详细的代码覆盖率情况,图中绿色的行表示已经被覆盖,红色的行表示尚未被覆盖,黄色的行表示部分覆盖;左侧绿色菱形块表示该分支已经被完全覆盖、黄色菱形块表示该分支仅被部分覆盖。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/6b/ce/6bf54542c3acb5f8e0d3bdf62ee9e7ce.png" alt="" />
|
||||
|
||||
**图2 JaCoCo详细代码覆盖率实例**
|
||||
|
||||
显然,通过这个详尽的报告,你就可以知道代码真实的执行情况、哪些代码未被覆盖。以此为基础,你再去设计测试用例就会更有针对性了。
|
||||
|
||||
## 代码覆盖率工具的实现原理
|
||||
|
||||
JaCoCo的详细报告,让你惊叹于代码覆盖率工具的强大。但你有没有仔细想过,这样的统计信息如何被获取到的呢?
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/0b/d5/0b8f26275681d8d5edde60487d70e8d5.png" alt="" /><br />
|
||||
**图3 统计代码覆盖率的不同注入实现技术**
|
||||
|
||||
**实现代码覆盖率的统计,最基本的方法就是注入(Instrumentation)。简单地说,注入就是在被测代码中自动插入用于覆盖率统计的探针(Probe)代码,并保证插入的探针代码不会给原代码带来任何影响。**
|
||||
|
||||
对于Java代码来讲,根据注入目标的不同,可以分为源代码(Source Code)注入和字节码(Byte Code)注入两大类。基于JVM本身特性以及执行效率的原因,目前主流的工具基本都是使用字节码注入,注入的具体实现采用ASM技术。
|
||||
|
||||
ASM是一个Java字节码操纵框架,能被用来动态生成类或者增强既有类的功能,可以直接产生 class 文件,也可以在类被加载入JVM之前动态改变类行为。
|
||||
|
||||
根据注入发生的时间点,字节码注入又可以分为两大模式:On-The-Fly注入模式和Offline注入模式。
|
||||
|
||||
**第一,On-The-Fly注入模式**
|
||||
|
||||
On-The-Fly模式的特点在于无需修改源代码,也无需提前进行字节码插桩。它适用于支持Java Agent的运行环境。
|
||||
|
||||
这样做的优点是,可以在系统不停机的情况下,实时收集代码覆盖率信息。缺点是运行环境必须允许使用Java Agent。
|
||||
|
||||
实现On-The-Fly模式,主要有两种技术方案:
|
||||
|
||||
<li>
|
||||
开发自定义的类装载器(Class Loader)实现类装载策略,每次类加载前,需要在class文件中插入探针,早期的Emma就是使用这种方案实现的探针插入;
|
||||
</li>
|
||||
<li>
|
||||
借助Java Agent,利用执行在main()方法之前的拦截器方法premain()来插入探针,实际使用过程中需要在JVM的启动参数中添加“-javaagent”并指定用于实时字节码注入的代理程序,这样代理程序在装载每个class文件前,先判断是否已经插入了探针,如果没有则需要将探针插入class文件中,目前主流的JaCoCo就是使用了这个方式。
|
||||
</li>
|
||||
|
||||
**第二,Offline注入模式**
|
||||
|
||||
Offline模式也无需修改源代码,但是需要在测试开始之前先对文件进行插桩,并事先生成插过桩的class文件。它适用于不支持Java Agent的运行环境,以及无法使用自定义类装载器的场景。
|
||||
|
||||
这样做的优点是,JVM启动时不再需要使用Java Agent额外开启代理,缺点是无法实时获取代码覆盖率信息,只能在系统停机时下获取。
|
||||
|
||||
Offline模式根据是生成新的class文件还是直接修改原class文件,又可以分为Replace和Inject两种不同模式。
|
||||
|
||||
和On-The-Fly注入模式不同,Replace和Inject的实现是,在测试运行前就已经通过ASM将探针插入了class文件,而在测试的运行过程中不需要任何额外的处理。Cobertura就是使用Offline模式的典型代表。
|
||||
|
||||
## 总结
|
||||
|
||||
测试覆盖率通常被用来衡量测试的充分性和完整性,包括面向项目的需求覆盖率和更偏向技术的代码覆盖率。而需求覆盖率的统计方式不再适用于现在的敏捷开发模式,所以现在谈到测试覆盖率,大多是指代码覆盖率。
|
||||
|
||||
但是,高的代码覆盖率不一定能保证软件的质量,因为代码覆盖率是基于现有代码,无法发现那些“未考虑某些输入”以及“未处理某些情况”形成的缺陷。
|
||||
|
||||
另外,对于代码覆盖率的统计工具,我希望你不仅仅是会用的层次,而是能够理解它们的原理,知其然知其所以然,才能更好地利用这些工具完成你的测试工作。
|
||||
|
||||
## 思考题
|
||||
|
||||
你在实际工作中,是否还接触过C/C++,JavaScript等语言的代码覆盖率工具,比如GCC Coverage、JSCoverage和Istanbul等?如果接触过的话,请你谈谈自己使用的感受以及遇到过的“坑”。
|
||||
|
||||
欢迎你给我留言。
|
||||
|
||||
|
||||
156
极客时间专栏/软件测试52讲/测试基础知识篇/07 | 如何高效填写软件缺陷报告?.md
Normal file
156
极客时间专栏/软件测试52讲/测试基础知识篇/07 | 如何高效填写软件缺陷报告?.md
Normal file
@@ -0,0 +1,156 @@
|
||||
<audio id="audio" title="07 | 如何高效填写软件缺陷报告?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/20/aa/205aa0f1cb31f915f636e881cd5897aa.mp3"></audio>
|
||||
|
||||
在上一篇文章中,我为你介绍了测试覆盖率的概念,并重点介绍了代码覆盖率的应用价值以及局限性。今天我会为你介绍如何才能写出一份高效的软件缺陷报告。
|
||||
|
||||
测试工程师需要利用对需求的理解、高效的执行力以及严密的逻辑推理能力,迅速找出软件中的潜在缺陷,并以缺陷报告的形式递交给开发团队,这看起来是不是有点像侦探柯南呢。
|
||||
|
||||
**缺陷报告是测试工程师与开发工程师交流沟通的重要桥梁,也是测试工程师日常工作的重要输出。** 作为优秀的测试工程师,最基本的一项技能就是,把发现的缺陷准确无歧义地表达清楚。
|
||||
|
||||
“准确无歧义地表达”意味着,开发工程师可以根据缺陷报告快速理解缺陷,并精确定位问题。同时,通过这个缺陷报告,开发经理可以准确预估缺陷修复的优先级、产品经理可以了解缺陷对用户或业务的影响以及严重性。
|
||||
|
||||
可见,缺陷报告本身的质量将直接关系到缺陷被修复的速度以及开发工程师的效率,同时还会影响测试工程师的信用、测试与开发人员协作的有效性。
|
||||
|
||||
那么,如何才能写出一份高效的缺陷报告呢?或者说,一份好的缺陷报告需要包括哪些具体内容呢?
|
||||
|
||||
你可能觉得这并不是什么难事儿,毕竟软件企业通常都有缺陷管理系统,比如典型的ALM(以前的Quality Center)、JIRA、Bugzilla、BugFree和Mantis等。当使用这类系统递交缺陷时,会自动生成模板,你只要按照其中的必填字段提供缺陷的详细信息就可以了。
|
||||
|
||||
很多时候,你不用想应该提供些什么信息,系统会引导你提供相关的信息。但是,你有仔细想过为什么要填写这些字段,这些字段都起什么作用,以及每个字段的内容具体应该怎么填写吗?
|
||||
|
||||
**你必须牢牢记住的是,好的缺陷报告绝对不是大量信息的堆叠,而是以高效的方式提供准确有用的信息。**
|
||||
|
||||
接下来,我就带你一起去看一份高效的缺陷报告主要由哪些部分组成,以及每部分内容的关键点是什么。
|
||||
|
||||
## 缺陷标题
|
||||
|
||||
缺陷标题通常是别人最先看到的部分,是对缺陷的概括性描述,通常采用“在什么情况下发生了什么问题”的模式。
|
||||
|
||||
**首先,对“什么问题”的描述不仅要做到清晰简洁,最关键是要足够具体,切忌不能采用过于笼统的描述。描述“什么问题”的同时还必须清楚地表述发生问题时的上下文,也就是问题出现的场景。**
|
||||
|
||||
“用户不能正常登录”“搜索功能有问题”和“用户信息页面的地址栏位置不正确”等,这样的描述会给人“说了等于没说”的感觉。这样的描述,很容易引发开发工程师的反感和抵触情绪,从而造成缺陷被拒绝修改(reject)。同时,还会造成缺陷管理上的困难以及过程的低效。
|
||||
|
||||
比如,当你发现了一个菜单栏上某个条目缺失的问题,在递交缺陷报告前,通常会去缺陷管理系统搜索一下是否已经有人递交过类似的缺陷。
|
||||
|
||||
当你以“菜单栏”为关键字搜索时,你可能会得到一堆“菜单栏有问题”的缺陷,如果缺陷标题的描述过于笼统,你就不得不点击进入每个已知缺陷点去看细节描述,这就会大大降低你的工作效率。
|
||||
|
||||
所以,如果缺陷标题本身就能概括性地描述具体问题,你就可以通过阅读标题判断类似的缺陷是否被提交过,大大提高测试工程师提交缺陷报告的效率。
|
||||
|
||||
**其次,标题应该尽可能描述问题本质,而避免只停留在问题的表面。**
|
||||
|
||||
比如,“商品金额输入框,可以输入英文字母和其他字符”这个描述就只描述了问题的表面现象,而采用诸如“商品金额输入框,没有对输入内容做校验”的方式,就可以透过标题看到缺陷的本质,这样可以帮助开发人员快速掌握问题的本质。
|
||||
|
||||
**最后,缺陷标题不易过长,对缺陷更详细的描述应该放在“缺陷概述”里。**
|
||||
|
||||
## 缺陷概述
|
||||
|
||||
缺陷概述通常会提供更多概括性的缺陷本质与现象的描述,是缺陷标题的细化。这部分内容通常是开发工程师打开缺陷报告后最先关注的内容,所以用清晰简短的语句将问题的本质描述清楚是关键。
|
||||
|
||||
缺陷概述还会包括缺陷的其他延展部分,比如你可以在这部分列出同一类型的缺陷可能出现的所有场景;再比如,你还可以描述同样的问题是否会在之前的版本中重现等。在这里,你应该尽量避免以缺陷重现步骤的形式来描述,而应该使用概括性的语句。
|
||||
|
||||
总之,**缺陷概述的目的是,清晰简洁地描述缺陷,使开发工程师能够聚焦缺陷的本质。**
|
||||
|
||||
## 缺陷影响
|
||||
|
||||
缺陷影响描述的是,缺陷引起的问题对用户或者对业务的影响范围以及严重程度。
|
||||
|
||||
缺陷影响决定了缺陷的优先级(Priority)和严重程度(Severity),开发经理会以此为依据来决定修复该缺陷的优先级;而产品经理会以此为依据来衡量缺陷的严重程度,并决定是否要等该缺陷被修复后才能发布产品。
|
||||
|
||||
**测试工程师准确描述缺陷影响的前提是,必须对软件的应用场景以及需求有深入的理解,这也是对测试工程师业务基本功的考验。**
|
||||
|
||||
## 环境配置
|
||||
|
||||
**环境配置用以详细描述测试环境的配置细节,为缺陷的重现提供必要的环境信息。** 比如,操作系统的类型与版本、被测软件版本、浏览器的种类和版本、被测软件的配置信息、集群的配置参数、中间件的版本信息等等。
|
||||
|
||||
**需要注意的是,环境配置的内容通常是按需描述,也就是说通常只描述那些重现缺陷的环境敏感信息。**
|
||||
|
||||
比如,“菜单栏上某个条目缺失的问题”只会发生在Chrome浏览器,而其他浏览器都没有类似问题。那么,Chrome浏览器就是环境敏感信息,必须予以描述,而至于Chrome浏览器是运行在什么操作系统上就无关紧要了,无需特意去描述了。
|
||||
|
||||
## 前置条件
|
||||
|
||||
**前置条件是指测试步骤开始前系统应该处在的状态,其目的是减少缺陷重现步骤的描述。合理地使用前置条件可以在描述缺陷重现步骤时排除不必要的干扰,使其更有针对性。**
|
||||
|
||||
比如,某个业务操作需要先完成用户登录,你在缺陷重现步骤里就没有必要描述登录操作的步骤细节,可以直接使用 “前置条件:用户已完成登录”的描述方式;
|
||||
|
||||
再比如,用户在执行登录操作前,需要事先在被测系统准备好待登录用户,你在描述时也无需增加“用测试数据生成工具生成用户”的步骤,可以直接使用 “前置条件:用户已完成注册”的描述方式。
|
||||
|
||||
## 缺陷重现步骤
|
||||
|
||||
缺陷重现步骤是整个缺陷报告中最核心的内容,其目的在于用简洁的语言向开发工程师展示缺陷重现的具体操作步骤。
|
||||
|
||||
这里需要注意的是,**操作步骤通常是从用户角度出发来描述的,每个步骤都应该是可操作并且是连贯的,所以往往会采用步骤列表的表现形式。**
|
||||
|
||||
通常测试工程师在写缺陷重现步骤前,需要反复执行这些步骤3次以上:一是,确保缺陷的可重现性;二是,找到最短的重现路径,过滤掉那些非必要的步骤,避免产生不必要的干扰。
|
||||
|
||||
对于缺陷重现步骤的描述应该尽量避免以下3个常见问题:
|
||||
|
||||
<li>
|
||||
笼统的描述,缺乏可操作的具体步骤。
|
||||
</li>
|
||||
<li>
|
||||
出现与缺陷重现不相关的步骤。
|
||||
</li>
|
||||
<li>
|
||||
缺乏对测试数据的相关描述。
|
||||
</li>
|
||||
|
||||
## 期望结果和实际结果
|
||||
|
||||
期望结果和实际结果通常和缺陷重现步骤绑定在一起,在描述重现步骤的过程中,需要明确说明期待结果和实际结果。期待结果来自于对需求的理解,而实际结果来自于测试执行的结果。
|
||||
|
||||
通常来讲,**当你描述期望结果时,需要说明应该发生什么,而不是什么不应该发生;而描述实际结果时,你应该说明发生了什么,而不是什么没有发生。**
|
||||
|
||||
## 优先级(Priority)和严重程度(Severity)
|
||||
|
||||
我之所以将优先级和严重程度放在一起,是因为这两个概念看起来有点类似,而本质却完全不同。而且,很多入行不久的测试工程师,也很难搞清楚这两者的差异到底在哪里。
|
||||
|
||||
根据百度百科的解释,缺陷优先级是指缺陷必须被修复的紧急程度,而缺陷严重程度是指因缺陷引起的故障对软件产品的影响程度。
|
||||
|
||||
可见,严重程度是缺陷本身的属性,通常确定后就不再变化,而优先级是缺陷的工程属性,会随着项目进度、解决缺陷的成本等因素而变动。那么,缺陷的优先级和严重程度又有什么关系呢?
|
||||
|
||||
<li>
|
||||
缺陷越严重,优先级就越高;
|
||||
</li>
|
||||
<li>
|
||||
缺陷影响的范围越大,优先级也会越高;
|
||||
</li>
|
||||
<li>
|
||||
有些缺陷虽然从用户影响角度来说不算严重,但是会妨碍测试或者是自动化测试的执行,这类缺陷属于典型的严重程度低,但是优先级高;
|
||||
</li>
|
||||
<li>
|
||||
有些缺陷虽然严重程度比较高,但是考虑到修复成本以及技术难度,也会出现优先级较低的情况。
|
||||
</li>
|
||||
|
||||
## 变通方案(Workaround)
|
||||
|
||||
变通方案是提供一种临时绕开当前缺陷而不影响产品功能的方式,通常由测试工程师或者开发工程师完成,或者他们一同决定。
|
||||
|
||||
变通方案的有无以及实施的难易程度,是决定缺陷优先级和严重程度的重要依据。如果某个严重的缺陷没有任何可行的变通方案,那么不管修复缺陷代价有多大,优先级一定会是最高的,但是如果该缺陷存在比较简单的变通方案,那么优先级就不一定会是最高的了。
|
||||
|
||||
## 根原因分析(Root Cause Analysis)
|
||||
|
||||
根原因分析就是我们平时常说的RCA,如果你能在发现缺陷的同时,定位出问题的根本原因,清楚地描述缺陷产生的原因并反馈给开发工程师,那么开发工程师修复缺陷的效率就会大幅提升,而且你的技术影响力也会被开发认可。
|
||||
|
||||
可以做好根原因分析的测试工程师,通常都具有开发背景,或者至少有较好的代码阅读以及代码调试的能力。
|
||||
|
||||
所以做为测试工程师,你很有必要去深入学习一门高级语言,这将帮助你体系化地建立起编程思想和方法,这样在之后的工作中,无论你是面对开发的代码,还是自动化测试代码和脚本都能做到得心应手,应对自如。
|
||||
|
||||
## 附件(Attachment)
|
||||
|
||||
附件通常是为缺陷的存在提供必要的证据支持,常见的附件有界面截图、测试用例日志、服务器端日志、GUI测试的执行视频等。
|
||||
|
||||
对于那些很难用文字描述清楚的GUI界面布局的缺陷,你可以采用截图并高亮显示应该关注的区域的方式去提交缺陷报告。
|
||||
|
||||
## 总结
|
||||
|
||||
缺陷报告是测试工程师与开发工程师交流沟通的重要桥梁,也是测试工程师日常工作的重要输出。
|
||||
|
||||
一份高效的软件缺陷报告,应该包括缺陷标题、缺陷概述、缺陷影响、环境配置、前置条件、缺陷重现步骤、期望结果和实际结果、优先级和严重程度、变通方案、根原因分析,以及附件这几大部分。
|
||||
|
||||
缺陷报告的每一部分内容,都会因为目的、表现形式有各自的侧重点,所以想要写出一份高效的软件缺陷报告,需要对其组成有深入的理解。
|
||||
|
||||
## 思考题
|
||||
|
||||
关于高效填写软件缺陷报告,你还有哪些好的实践?
|
||||
|
||||
欢迎你给我留言。
|
||||
|
||||
|
||||
165
极客时间专栏/软件测试52讲/测试基础知识篇/08 | 以终为始,如何才能做好测试计划?.md
Normal file
165
极客时间专栏/软件测试52讲/测试基础知识篇/08 | 以终为始,如何才能做好测试计划?.md
Normal file
@@ -0,0 +1,165 @@
|
||||
<audio id="audio" title="08 | 以终为始,如何才能做好测试计划?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/b7/30/b78f35507310e4a81dcb0359ca36ba30.mp3"></audio>
|
||||
|
||||
在上一篇文章中,我为你介绍了如何高效填写软件缺陷报告,并为你解读了缺陷报告中的关键内容。今天,我将为你介绍一份成功的测试计划应该包含哪些内容,以及如何才能做好测试计划。
|
||||
|
||||
软件项目,通常都会有详细的项目计划。软件测试作为整个项目中的重要一环,也要执行详细的测试计划。正所谓运筹帷幄之中,决胜千里之外,强调的就是预先计划的重要性和必要性。
|
||||
|
||||
在早期的软件工程实践中,软件测试计划的制定通常是在需求分析以及测试需求分析完成后开始,并且是整个软件研发生命周期中的重要环节。
|
||||
|
||||
但是,在敏捷开发模式下,你可能会有这样的疑问,软件测试计划还有那么重要吗?我所在的软件项目压根儿就没有正式的测试计划,不也没出什么大问题吗?
|
||||
|
||||
的确,对于很多非产品型的互联网公司,由于采用了敏捷开发模式,的确很少去制定传统意义上的测试计划了,但这并不是说它们就不再制定测试计划了。
|
||||
|
||||
只不过是,测试计划的表现形式已经不再是传统意义上庞大的、正式的测试计划文档了,而更多的是体现在每个迭代(sprint)的计划环节,而且这样的短期测试计划可以非常迅速地根据项目情况实时调整。
|
||||
|
||||
所以说,**测试计划依旧存在,只是从原来的一次性集中制定测试计划,变成了以迭代的方式持续制定测试计划。** 但是对于传统软件企业,或者是做非互联网软件产品的企业,它们通常还是会有非常正式的软件测试计划。
|
||||
|
||||
由此可见,无论对于早期最具典型性的瀑布开发模型,还是现在的敏捷开发模型,测试计划的重要性始终没有发生变化。那么,你可能会问,测试计划的重要性到底体现在哪些方面呢?在回答这个问题之前,我先跟你聊聊如果没有测试计划会带来什么问题。
|
||||
|
||||
## 没有测试计划会怎么样?
|
||||
|
||||
如果没有测试计划,会带来哪些问题呢?
|
||||
|
||||
<li>
|
||||
很难确切地知道具体的测试范围,以及应该采取的具体测试策略;
|
||||
</li>
|
||||
<li>
|
||||
很难预估具体的工作量和所需要的测试工程师数量,同时还会造成各个测试工程师的分工不明确,引发某些测试工作被重复执行而有些测试则被遗漏的问题;
|
||||
</li>
|
||||
<li>
|
||||
测试的整体进度完全不可控,甚至很难确切知道目前测试的完成情况,对于测试完成时间就更难预估准确的时间节点了;
|
||||
</li>
|
||||
<li>
|
||||
整个项目对潜在风险的抵抗能力很弱,很难应对需求的变更以及其他突发事件。
|
||||
</li>
|
||||
|
||||
从这些问题中,你可以逆向思维推导出,一份好的测试计划要包括:测试范围、测试策略、测试资源、测试进度和测试风险预估,这五大方面,并且每一部分都要给出应对可能出现问题的解决办法。
|
||||
|
||||
## 测试范围
|
||||
|
||||
顾名思义,测试范围描述的是被测对象以及主要的测试内容。
|
||||
|
||||
比如,对于用户登录模块,功能测试既需要测试浏览器端又需要测试移动端,同时还考虑登录的安全和并发性能相关的非功能性需求的测试等。
|
||||
|
||||
测试范围的确定通常是在测试需求分析完成后进行,所以确定测试范围的过程在一定程度上也是对测试需求分析的进一步检验,这将有助于在早期阶段就发现潜在的测试遗漏。
|
||||
|
||||
同时,由于不可能进行穷尽测试,而且测试的时间和资源都是有限的,所以必须有所取舍,进行有针对性的测试。**因此,测试范围中需要明确“测什么”和“不测什么”。**
|
||||
|
||||
## 测试策略的话题
|
||||
|
||||
**测试策略简单来讲就是需要明确“先测什么后测什么”和“如何来测”这两个问题。**
|
||||
|
||||
病有轻重缓急,测试也是一样的道理,重要的项先测,而不重要的项要后测。**测试策略会要求我们明确测试的重点,以及各项测试的先后顺序。**
|
||||
|
||||
比如,对用户登录模块来讲,“用户无法正常登录”和“用户无法重置密码”这两个潜在问题,对业务的影响孰轻孰重一目了然,所以,你应该按照优先级来先测“用户正常登录”,再测“用户重置密码”。
|
||||
|
||||
**测试策略还需要说明,采用什么样的测试类型和测试方法。** 这里需要注意的是,不仅要给出为什么要选用这个测试类型,还要详细说明具体的实施方法。
|
||||
|
||||
**第一,功能测试**
|
||||
|
||||
对于功能测试,你应该根据测试需求分析的思维导图来设计测试用例。
|
||||
|
||||
主线业务的功能测试由于经常需要执行回归测试,所以你需要考虑实施自动化测试,并且根据项目技术栈和测试团队成员的习惯与能力来选择合适的自动化测试框架。
|
||||
|
||||
**这里需要注意的是,你通常应该先实现主干业务流程的测试自动化。**
|
||||
|
||||
实际操作时,你通常需要先列出主要的功能测试点,并决定哪些测试点适合采用自动化测试,并且决定具体使用什么样的框架和技术。
|
||||
|
||||
对于需要手工测试的测试点,你要决定采用什么类型的测试用例设计方法,以及如何准备相关的测试数据。
|
||||
|
||||
另外,你还要评估被测软件的可测试性,如果有可测试性的问题,需要提前考虑切实可行的变通方案,甚至要求开发人员提供可测试性的接口。
|
||||
|
||||
**第二,兼容性测试**
|
||||
|
||||
对于兼容性测试来说,Web测试需要确定覆盖的浏览器类型和版本,移动设备测试需要确定覆盖的设备类型和具体iOS/Android的版本等。
|
||||
|
||||
你可能会问,我要怎么确定需要覆盖的移动设备类型以及iOS/Android的版本列表呢?这个问题其实并不难:
|
||||
|
||||
- 如果是既有产品,你可以通过大数据技术分析产品的历史数据得出Top 30%的移动设备以及iOS/Android的版本列表,那么兼容性测试只需覆盖这部分即可。
|
||||
- 如果是一个全新的产品,你可以通过TalkingData这样的网站来查看目前主流的移动设备,分辨率大小、iOS/Android版本等信息来确定测试范围。
|
||||
|
||||
兼容性测试的实施,往往是在功能测试的后期,也就是说需要等功能基本都稳定了,才会开始兼容性测试。
|
||||
|
||||
当然也有特例,比如,对于前端引入了新的前端框架或者组件库,往往就会先在前期做兼容性评估,以确保不会引入后期无法解决的兼容性问题。
|
||||
|
||||
兼容性测试用例的选取,往往来自于已经实现的自动化测试用例。道理很简单,因为兼容性测试往往要覆盖最常用的业务场景,而这些最常用的业务场景通常也是首批实现自动化测试的目标。
|
||||
|
||||
所以,我们的GUI自动化框架,就需要能够支持同一套测试脚本在不做修改的前提下,运行于不同的浏览器。
|
||||
|
||||
**第三,性能测试**
|
||||
|
||||
对于性能测试,需要在明确了性能需求(并发用户数、响应时间、事务吞吐量等)的前提下,结合被测系统的特点,设计性能测试场景并确定性能测试框架。
|
||||
|
||||
比如,是直接在API级别发起压力测试,还是必须模拟终端用户行为进行基于协议的压力测试。再比如,是基于模块进行压力测试,还是发起全链路压测。
|
||||
|
||||
如果性能是背景数据敏感的场景,还需要确定背景数据量级与分布,并决定产生背景数据的技术方案,比如是通过API并发调用来产生测试数据,还是直接在数据库上做批量insert和update操作,或者是两种方式的结合。
|
||||
|
||||
最后,无论采用哪种方式,都需要明确待开发的单用户脚本的数量,以便后续能够顺利组装压测测试场景。
|
||||
|
||||
性能测试的实施,是一个比较复杂的问题。首先,需要根据你想要解决的问题,确定性能测试的类型;然后,根据具体的性能测试类型开展测试。
|
||||
|
||||
<li>
|
||||
性能测试的实施,往往先要根据业务场景来决定需要开发哪些单用户脚本,脚本的开发会涉及到很多性能测试脚本特有的概念,比如思考时间、集合点、动态关联等等。
|
||||
</li>
|
||||
<li>
|
||||
脚本开发完成后,你还要以脚本为单位组织测试场景(Scenario),场景定义简单来说就是百分之多少的用户在做登录、百分之多少的用户在做查询、每个用户的操作步骤之间需要等待多少时间、并发用户的增速是5秒一个,还是5秒2个等等。
|
||||
</li>
|
||||
<li>
|
||||
最后,才是具体的测试场景执行。和自动化功能测试不同,性能测试执行完成后性能测试报告的解读,是整个测试过程中最关键的点。
|
||||
</li>
|
||||
|
||||
如果你现在不太清楚我上面提到的一些概念也没关系,我会在后续的文章中为你详细讲解。
|
||||
|
||||
除了我给你详细分析的、最常用的功能测试、兼容性测试和性能测试外,还有很多测试类型(比如,接口测试、集成测试、安全测试、容量验证、安装测试、故障恢复测试等)。这些测试类型,都有各自的应用场景,也相应有独特的测试方法,在这里我就不再一一展开了,如果你有关于这些测试类型的问题,可以给我留言讨论。
|
||||
|
||||
## 测试资源
|
||||
|
||||
测试资源通常包括测试人员和测试环境,这两类资源都是有限的。测试计划的目的就是,保证在有限资源下的产出最大化。所以,**测试资源就是需要明确“谁来测”和“在哪里测”这两个问题。**
|
||||
|
||||
测试人员是最重要的,直接关系到整个测试项目的成败和效率。测试人员的资源通常有两个维度:一是,测试工程师的数量;二是,测试工程师的个人经验和能力。
|
||||
|
||||
你会发现,测试工程师的经验和能力不足,很难通过测试人员的数量来弥补。相反地,测试工程师的经验和能力都非常强的情况下,测试人员的数量可以适当地减少。
|
||||
|
||||
通常在测试团队中,测试工程师既有资深,也会有初级,那么你就必须针对团队的实际情况去安排测试计划。比如,难度较大的工作,或者一些新工具、新方法的应用,又或者自动化测试开发工作,通常由资深的测试工程师来承担;而那些相对机械性、难度较小的工作,则由初级工程师完成。
|
||||
|
||||
可见,你要想规划好测试资源,除了要了解项目本身外,还必须对测试团队的人员特点有清晰的把控。另外,我强烈建议你把具体的任务清晰地落实到每个人的身上,这将有利于建立清晰的责任机制,避免后续可能发生的扯皮。
|
||||
|
||||
相对于测试人员,测试环境就比较好理解了。不同的项目,可能会使用共享的测试环境,也可能使用专用的测试环境,甚至还会直接使用生产环境。另外,对于目前一些已经实现容器化部署与发布的项目,测试环境就会变得更简单与轻量级,这部分内容我会在后续的文章中给你详细讲解。
|
||||
|
||||
## 测试进度
|
||||
|
||||
在明确了测试范围、测试策略和测试资源之后,你就要考虑具体的测试进度了。**测试进度主要描述各类测试的开始时间,所需工作量,预计完成时间,并以此为依据来建议最终产品的上线发布时间。**
|
||||
|
||||
比如,版本接受测试(Build Acceptance Test)的工作量,冒烟测试(Smoke Test)的工作量,自动化脚本开发的工作量,缺陷修复的验证工作量,需要几轮回归测试、每一轮回归测试的工作量等等。
|
||||
|
||||
在传统瀑布模型中,测试进度完全依赖于开发完成并递交测试版本的时间。如果开发提交测试版本发生了延误,那么在不裁剪测试需求的情况下,产品整体的上线时间就同样会延期。
|
||||
|
||||
然而在敏捷模式下,测试活动贯穿于整个开发过程,很多测试工作会和开发工作同步进行,比如采用行为驱动开发(Behavior-Driven Development)模式,这样测试进度就不会完全依赖于开发递交可测试版本的时间。
|
||||
|
||||
行为驱动开发,就是平时我们经常说的BDD,指的是可以通过自然语言书写非程序员可读的测试用例,并通过StepDef来关联基于自然语言的步骤描述和具体的业务操作,最典型的框架就是知名“Cucumber”。
|
||||
|
||||
## 测试风险预估
|
||||
|
||||
俗话说,计划赶不上变化,对于测试也是一样的道理,很少有整个测试过程是完全按照原本测试计划执行的。通常需求变更、开发延期、发现重大缺陷和人员变动是引入项目测试风险的主要原因。
|
||||
|
||||
对于需求变更,比如增加需求、删减需求、修改需求等,一定要重新进行测试需求分析,确定变更后的测试范围和资源评估,并与项目经理和产品经理及时沟通因此引起的测试进度变化。测试经理/测试负责人切忌不能有自己咬牙扛过去的想法,否则无论是对测试团队还是对产品本身都不会有任何好处。
|
||||
|
||||
另外,随着测试的开展,你可能会发现前期对于测试工作量的预估不够准确,也可能发现需要增加更多的测试类型,也可能发现因为要修改测试架构的严重缺陷而导致很多的测试需要全回归,还有可能出现开发递交测试版本延期,或者人员变动等各种情况。
|
||||
|
||||
所以,**在制定测试计划时,你就要预估整个测试过程中可能存在的潜在风险,以及当这些风险发生时的应对策略。** 那么,在真的遇到类似问题时,你才可以做到心中不慌,有条不紊地应对这些挑战。
|
||||
|
||||
## 总结
|
||||
|
||||
软件测试同软件项目一样,也要制定详细的测试计划。虽然在敏捷开发模式下,软件测试不再局限于厚重的、正式的计划文档,但是测试计划的重要性丝毫没有发生变化。
|
||||
|
||||
一份成功的测试计划,必须清楚地描述:测试范围、测试策略、测试资源、测试进度和测试风险预估这五个最重要的方面。
|
||||
|
||||
测试范围需要明确“测什么”和“不测什么”;测试策略需要明确“先测什么后测什么”和“如何来测”;测试资源需要明确“谁来测”和“在哪里测”;测试进度是需要明确各类测试的开始时间,所需工作量和预计完成时间;测试风险预估是需要明确如何有效应对各种潜在的变化。
|
||||
|
||||
## 思考题
|
||||
|
||||
在这篇文章中,我只和你分享了做好测试计划中最最关键的内容,意在抛砖引玉。那么,你在工程实践中,还有哪些见解呢?
|
||||
|
||||
欢迎你给我留言。
|
||||
|
||||
|
||||
170
极客时间专栏/软件测试52讲/测试基础知识篇/09 | 软件测试工程师的核心竞争力是什么?.md
Normal file
170
极客时间专栏/软件测试52讲/测试基础知识篇/09 | 软件测试工程师的核心竞争力是什么?.md
Normal file
@@ -0,0 +1,170 @@
|
||||
<audio id="audio" title="09 | 软件测试工程师的核心竞争力是什么?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/59/93/594b748e04755f411b0efe54cea78893.mp3"></audio>
|
||||
|
||||
在前面的文章中,我给你介绍了测试工程师应该具备的一些基础知识,包括如何设计测试用例、如何制定测试计划、什么是测试覆盖率,以及软件生命周期各个阶段的自动化技术等等内容,相信你通过这些文章,或温故知新,或拓展视野,希望你有所收获。
|
||||
|
||||
那么,在介绍完这些比较基础的内容后,今天我就来和你聊聊测试工程师的核心竞争力到底什么。只有当你真正明白了自己的核心竞争力,你才能理清“应该做什么”和“应该怎么做”这两个问题,才能朝着正确的方向前行。
|
||||
|
||||
我以我们团队招聘功能测试和测试开发工程师为例,带你了解一下测试工程师的核心竞争力到底是什么。
|
||||
|
||||
<li>
|
||||
<p>案例一来自我们的资深功能测试工程师招聘。当时,有一位拥有近9年测试经验的资深测试候选人,我对他的简历还是比较满意的,所以就安排了面谈。但是,在聊的过程中我很快发现,这位候选人绝大多数的测试经验积累都“强”绑定在特定的业务领域。<br />
|
||||
如果抛开这个特定的业务领域,他对测试技术本身以及产品技术实现都缺乏系统的思考和理解。换言之,他的价值仅仅能够体现在这个特定的产品业务上,而一旦离开了这个业务领域,他的经验积累很难被有效重用,也就是说他很难快速适应并胜任我们的业务领域测试。所以,他最终没有得到我们的offer。<br />
|
||||
**从这个案例中,你可以看出作为测试人员,必须要深入理解业务,但是业务知识不能等同于测试能力。**</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>案例二来自我们的测试开发岗位招聘。当时,有一位5年测试开发从业经验的候选人,是南京大学软件学院的硕士,毕业后一直在国内的互联网巨头公司从事测试框架和工具平台的开发工作。<br />
|
||||
看完他的简历,我发现他参与开发过的测试框架和工具和我们当时在做的项目很匹配,加之他的背景也相当不错,内心感<!-- [[[read_end]]] -->觉这个职位基本就是他的了。但是,面谈结束后,我彻底改变了想法。<br />
|
||||
他所做的的确是测试框架和工具平台的开发工作,但是他的核心能力纯粹就是开发,他只关注如何实现预先设计的功能,而完全不关心所开发的测试框架和工具平台在测试中的具体应用场景。<br />
|
||||
我承认他的开发能力,但他并不能胜任我们的测试开发岗位。因为,**测试开发岗位的核心其实是“测试”,“开发”的目的是更好地服务于测试**,我们看重的是对测试的理解,以及在此基础上设计、开发帮助测试人员提高效率并解决实际问题的工具,而不是一个按部就班、纯粹意义上的开发人员。</p>
|
||||
</li>
|
||||
|
||||
这两个实际案例,是否已经引发你去思考这样一个问题:什么才是测试工程师的核心竞争力?
|
||||
|
||||
目前的测试工程师分为两大类别,一类是做业务功能测试的,另一类是做测试开发的,二者的核心竞争力有很大差别。那么,接下来我就带你一起去看看,功能测试和测试开发工程师的核心竞争力分别是什么。
|
||||
|
||||
我先带你看看业务功能测试工程师,也就是传统意义上的测试工程师的核心竞争力,我归纳了以下几点。
|
||||
|
||||
## 传统测试工程师师应该具备的核心竞争力
|
||||
|
||||
这部分内容,我按照一项能力对测试工程师的重要程度的顺序,给你依次归纳了测试工程师要具备的七项核心竞争力,包括:测试策略设计能力、测试用例设计能力、快速学习能力、探索性测试思维、缺陷分析能力、自动化测试技术和良好的沟通能力。
|
||||
|
||||
或许,你感觉测试策略设计能力、探索性测试思维等对资深的测试工程师来说更重要,而你现在还处在培养快速学习能力、沟通能力、测试用例设计能力的维度。那也没有关系,不断地学习、丰富自己的知识体系,具备更强的职场竞争力,不正是你在追求的吗?
|
||||
|
||||
所以,我在分析你应该具备的主要能力的同时,也会给你分享如何才能使自己具备这些能力 ,帮你成就更好的自己。
|
||||
|
||||
**第一项核心竞争力,测试策略设计能力**
|
||||
|
||||
测试策略设计能力是指,对于各种不同的被测软件,能够快速准确地理解需求,并在有限的时间和资源下,明确测试重点以及最适合的测试方法的能力。
|
||||
|
||||
具备出色的测试策略设计能力,你可以非常明确地回答出测试过程中遇到的这些关键问题:
|
||||
|
||||
<li>
|
||||
测试要具体执行到什么程度;
|
||||
</li>
|
||||
<li>
|
||||
测试需要借助于什么工具;
|
||||
</li>
|
||||
<li>
|
||||
如何运用自动化测试以及自动化测试框架,以及如何选型;
|
||||
</li>
|
||||
<li>
|
||||
测试人员资源如何合理分配;
|
||||
</li>
|
||||
<li>
|
||||
测试进度如何安排;
|
||||
</li>
|
||||
<li>
|
||||
测试风险如何应对。
|
||||
</li>
|
||||
|
||||
培养出色的测试策略设计能力,不是一朝一夕的事情,通常需要经过大量项目的实际历练,并且你还要保持持续思考,主动去提炼共性的内容。
|
||||
|
||||
不像测试技术,你可以通过培训或者网上资料的学习而有快速的提升,**测试策略设计能力一定是需要你在大量实践的基础上潜移默化形成的。**
|
||||
|
||||
**我认为,测试策略设计能力是功能测试工程师最核心的竞争力,也是最难培养的。**
|
||||
|
||||
**第二项核心竞争力,测试用例设计能力**
|
||||
|
||||
测试用例设计能力是指,无论对于什么类型的测试,都能设计出高效地发现缺陷,保证产品质量的优秀测试用例。
|
||||
|
||||
要做好测试用例设计,不仅需要深入理解被测软件的业务需求和目标用户的使用习惯,还要熟悉软件的具体设计和运行环境,包括技术架构、缓存机制、中间件技术、第三方服务集成等等。
|
||||
|
||||
测试用例设计能力要求你不仅仅局限于熟悉业务领域的测试用例设计,而是能够融会贯通,熟练地把系统性的测试设计方法和具体业务有机结合,对任何被测软件都可以输出出色的测试用例。
|
||||
|
||||
**要想提高测试用例设计能力,你平时就要多积累,对常见的缺陷模式、典型的错误类型以及遇到过的缺陷,要不断地总结、归纳,才能逐渐形成体系化的用例设计思维。**
|
||||
|
||||
**同时,你还可以阅读一些好的测试用例设计实例开阔思路,日后遇到类似的被测系统时,可以做到融会贯通和举一反三。**
|
||||
|
||||
**第三项核心竞争力,快速学习能力**
|
||||
|
||||
快速学习能力,包含两个层面的含义:
|
||||
|
||||
<li>
|
||||
对不同业务需求和功能的快速学习与理解能力;
|
||||
</li>
|
||||
<li>
|
||||
对于测试新技术和新方法的学习与应用能力。
|
||||
</li>
|
||||
|
||||
显然,快速学习能力是各行业从业者应该具备的能力,但为什么我会单独列出来呢?
|
||||
|
||||
现今的软件项目,尤其是互联网项目,生命周期通常以“月”甚至是以“周”、“小时”为单位,一个测试工程师需要接触各种类型的测试项目,而不再像早年,可以在很长一段时间内只从事一个产品或者相关产品的测试了,所以快速学习能力对测试工程师来说,就是至关重要了,否则就容易被淘汰。
|
||||
|
||||
快速学习能力,乍一看是比较难培养的,但其实也有一些小窍门。
|
||||
|
||||
比如,当你学习一个新的开源工具时,建议你直接看官方文档:一来,这里的内容是最新而且是最权威的;二来,可以避免网上信息质量的参差不齐。知识输入源头是单一,而且权威的话,你的学习曲线也必然会比较平滑。
|
||||
|
||||
另外,当学习新内容时,你一定要做到理解其原理,而不是只停留在表面的、简单的操作和使用,长期保持这种学习状态,可以在很大程度上提高逻辑思维和理解能力。这样,当你再面对其他新鲜事物时候,也会更容易理解,形成良性循环。
|
||||
|
||||
**第四项核心竞争力,探索性测试思维**
|
||||
|
||||
探索性测试是指,测试工程师在执行测试的过程中不断学习被测系统,同时结合基于自己经验的错误猜测和逻辑推理,整理和分析出更多的有针对性的测试关注点。
|
||||
|
||||
本质上,探索性测试思维是“测试用例设计能力”和“快速学习能力”有机结合的必然结果。优秀的探索性测试思维可以帮助你实现低成本的“精准测试”,精准测试最通俗的理解可以概括为针对开发代码的变更,目标明确并且有针对性地对变更点以及变更关联点做测试,这也是目前敏捷测试主推的测试实践之一。
|
||||
|
||||
**第五项核心竞争力,缺陷分析能力**
|
||||
|
||||
缺陷分析能力,通常包含三个层面的含义:
|
||||
|
||||
<li>
|
||||
对于已经发现的缺陷,结合发生错误的上下文以及后台日志,可以预测或者定位缺陷的发生原因,甚至可以明确指出具体出错的代码行,由此可以大幅缩短缺陷的修复周期,并提高开发工程师对于测试工程师的认可以及信任度;
|
||||
</li>
|
||||
<li>
|
||||
根据已经发现的缺陷,结合探索性测试思维,推断同类缺陷存在的可能性,并由此找出所有相关的潜在缺陷;
|
||||
</li>
|
||||
<li>
|
||||
可以对一段时间内所发生的缺陷类型和趋势进行合理分析,由点到面预估整体质量的健康状态,并能够对高频缺陷类型提供系统性的发现和预防措施,并以此来调整后续的测试策略。
|
||||
</li>
|
||||
|
||||
这三个层面是依次递进的关系,越往后越能体现出测试工程师的核心竞争力。
|
||||
|
||||
**第六项核心竞争力,自动化测试技术**
|
||||
|
||||
掌握自动化测试技术,可以把你从大量的重复性手工劳动中解放出来,这样你可以把更多的时间花在更多类型的测试上。
|
||||
|
||||
一方面,自动化测试技术本身不绑定被测对象,比如说你掌握了GUI的自动化测试技术,那么你就可以基于这个技术去做任何GUI系统的界面功能测试了。
|
||||
|
||||
另一方面,自动化测试技术需要测试工程师具备一定的写代码的能力,这通常与测试工程师职业发展的诉求不谋而合,所以你会看到很多测试工程师非常热衷做自动化测试。
|
||||
|
||||
但是切记,自动化测试的核心价值还是“测试”本身,“自动化”仅仅是手段,实际工作中千万不要本末倒置,把大量的精力放在“自动化”上,一味追求自动化而把本质的“测试”弱化了。
|
||||
|
||||
**第七项核心竞争力,良好的沟通能力**
|
||||
|
||||
测试工程师在软件项目中作用,有点像“润滑剂”:
|
||||
|
||||
- 一方面,你需要对接产品经理和项目经理,以确保需求的正确实现和项目整体质量的达标;
|
||||
- 另一方面,你还要和开发人员不断地沟通、协调,确保缺陷的及时修复与验证。
|
||||
|
||||
所以,测试工程师的沟通能力会直接影响事务开展的效率。良好清晰的沟通能力,是一个技术优秀的测试工程师能否获得更大发展的“敲门砖”,也是资深测试工程师或者测试主管的核心竞争力。
|
||||
|
||||
## 测试开发工程师的核心竞争力
|
||||
|
||||
接下来,我再带你一起看看测试开发工程师的核心竞争力。
|
||||
|
||||
首先既然是测试开发工程师,那么代码开发能力是最基本的要求。可以说,一个合格的测试开发工程师一定可以成为一个合格的开发工程师,但是一个合格的开发工程师不一定可以成为合格的测试开发工程师。这也就是案例二中的候选人没有通过面试的原因。
|
||||
|
||||
**第一项核心竞争力,测试系统需求分析能力**
|
||||
|
||||
除了代码开发能力,测试开发工程师更要具备测试系统需求分析的能力。你要能够站在测试架构师的高度,识别出测试基础架构的需求和提高效率的应用场景。从这个角度说,你更像个产品经理,只不过你这个产品是为了软件测试服务的。
|
||||
|
||||
**第二项核心竞争力,更宽广的知识体系**
|
||||
|
||||
测试开发工程师需要具备非常宽广的知识体系,你不仅需要和传统的测试开发工程师打交道,因为他们是你构建的测试工具或者平台的用户;而且还要和CI/CD、和运维工程师们有紧密的联系,因为你构建的测试工具或者平台,需要接入到CI/CD的流水线以及运维的监控系统中去。
|
||||
|
||||
除此之外,你还要了解更高级别的测试架构部署和生产架构部署、你还必须对开发采用的各种技术非常熟悉。可见,对于测试开发工程师的核心竞争力要求是非常高的,这也就是为什么现今市场上资深的测试开发工程师的价格会高于资深的开发工程师的原因。
|
||||
|
||||
## 总结
|
||||
|
||||
我把测试工程师按照工作内容,分为了功能测试工程师(即传统测试工程师)和测试开发工程师两类,分别给你分享了他们的核心竞争力。
|
||||
|
||||
对于功能测试工程师来说,其核心竞争力包括:测试策略设计能力、测试用例设计能力、快速学习能力、探索性测试思维、缺陷分析能力、自动化测试技术和良好的沟通能力这七大部分,你可以有针对性地提升自己某方面的能力,去获取更大发展空间的“敲门砖”。
|
||||
|
||||
而对于测试开发工程师来说,你需要具备优秀的测试系统需求分析能力和完备的知识体系,这样才能保证你设计的测试工作和平台,可以更好地满足提升测试效率的要求。
|
||||
|
||||
## 思考题
|
||||
|
||||
你有没有想过这样一个问题,你很少会听到开发工程师谈论自己的核心竞争力,往往都是测试工程师更关注这个问题,这是不是从某个侧面反映出测试工程师的核心竞争力不够清晰或者是随着互联网时代的到来而发生了很大变化,说说你的看法吧。
|
||||
|
||||
欢迎你给我留言。
|
||||
|
||||
|
||||
121
极客时间专栏/软件测试52讲/测试基础知识篇/10 | 软件测试工程师需要掌握的非测试知识有哪些?.md
Normal file
121
极客时间专栏/软件测试52讲/测试基础知识篇/10 | 软件测试工程师需要掌握的非测试知识有哪些?.md
Normal file
@@ -0,0 +1,121 @@
|
||||
<audio id="audio" title="10 | 软件测试工程师需要掌握的非测试知识有哪些?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/0d/a3/0d6545ae4be3cab46d6406bb0efc1ba3.mp3"></audio>
|
||||
|
||||
我在上一篇文章中,跟你分享了测试工程师应该具备的核心竞争力,大多是测试专业知识方面的内容。但是,在专栏第一篇文章中,我提到了这样一个观点:一个优秀的测试工程师,必须具备宽广的知识面,才能设计出有的放矢的测试用例,保证整个软件产品的质量。
|
||||
|
||||
所以,今天我要分享的主题就是,除了测试专业知识外,你还要掌握哪些知识,才能一路披荆斩棘,成长为一名优秀的测试工程师,或者是测试架构师。
|
||||
|
||||
与开发工程师相比,你需要了解的技术种类要多得多,视野也要宽广很多,只是在每类技术的深度方面不如开发工程师。
|
||||
|
||||
**你可以参照下面这个比喻,来理解开发工程师和测试工程师的对知识的要求:开发工程师通常是“深度遍历”,关注的是“点”;而测试工程师通常是“广度遍历”,关注的是“面”。**
|
||||
|
||||
那么,测试工程师需要掌握的非测试知识主要有哪些呢?
|
||||
|
||||
如果你花时间静下心来仔细想一下,很可能会把自己吓一大跳,需要了解掌握的非测试知识实在是太多了,这简直就是一个mini版的系统架构师啊!
|
||||
|
||||
- 小到Linux/Unix/Windows操作系统的基础知识,Oracle/MySQL等传统关系型数据库技术,NoSQL非关系型数据库技术,中间件技术,Shell/Python脚本开发,版本管理工具与策略,CI/CD流水线设计,F5负载均衡技术,Fiddler/Wireshark/Tcpdump等抓包工具,浏览器Developer Tool等;
|
||||
- 大到网站架构设计,容器技术,微服务架构,服务网格(Service Mesh),DevOps,云计算,大数据,人工智能和区块链技术等。
|
||||
|
||||
可以说,测试工程师需要掌握的这些技术,几乎涵盖了当今主流软件技术的方方面面。当然,你也不可能一口气吃成胖子,所以我就挑选了几个我认为比较重要,又符合当前技术趋势的关键知识点,和你分享。
|
||||
|
||||
希望我的分享,可以帮助你在面对新的技术趋势时,站在更高的高度,更好地把握测试工作的内涵和外延。
|
||||
|
||||
## 网站架构的核心知识
|
||||
|
||||
现如今,互联网产品已经占据了软件行业的大半壁以江山。作为测试工程师,你很多时候都在和互联网产品,尤其是网站类应用产品的测试打交道。
|
||||
|
||||
这时,如果你想要做好互联网产品功能测试以外的其他测试,比如性能测试、稳定性测试、全链路压测、故障切换(Failover)测试、动态集群容量伸缩测试、服务降级测试和安全渗透测试等,就要掌握网站的架构知识。否则,面对这类测试时,你将束手无策。
|
||||
|
||||
- 比如,如果你不清楚Memcached这类分布式缓存集群的应用场景和基本原理,如果你不清楚缓存击穿、缓存雪崩、缓存预热、缓存集群扩容局限性等问题,你就设计不出针对缓存系统特有问题的测试用例;
|
||||
- 再比如,如果你对网站的可伸缩性架构设计不了解,不清楚应用服务器的各种负载均衡实现的基本原理,不了解数据库的读写分离技术,你就无法完成诸如故障切换、动态集群容量伸缩、服务降级等相关的测试,同时对于性能测试和全链路压测过程中可能遇到的各种瓶颈,也会很难定位和调整。
|
||||
|
||||
这就有点像当年做传统软件产品测试时,我们必须了解软件的架构设计一样,现在被测对象成了互联网产品,我们就必须要了解网站架构。
|
||||
|
||||
所以,我强烈建议你要掌握网站架构的核心知识,你不需要像系统架构师那样能够熟练驾驭各种架构,并根据业务选型,但你至少需要理解架构相关的基本知识以及核心原理。
|
||||
|
||||
基于此,我在专栏的最后安排了一系列文章,包括了网站高性能架构设计、网站高可用架构设计、网站伸缩性架构设计和网站可扩展性架构设计,为你详细讲解互联网架构的核心知识,提升你的互联网产品测试能力。
|
||||
|
||||
## 容器技术
|
||||
|
||||
“容器”已不再是一个陌生词汇了,大多数人都在实际工作中或多或少地用到了容器技术。与传统的虚拟机相比,容器技术在轻量化程度、资源占用、运行效率等方面具有压倒性的优势。
|
||||
|
||||
除了那些专门做容器测试的测试工程师外,一般的测试工程师接触容器技术的机会也越来越多。
|
||||
|
||||
很多中大型互联网企业都在推行容器化开发与运维,开发人员递交给测试工程师的软件版本通常就是一个Docker Image,直接在容器上进行测试。有些公司还会把测试用例和执行框架也打包成Docker Image,配合版本管理机制,实现用容器测试容器。
|
||||
|
||||
对测试开发工程师来说,需要应用容器的场景就更多了。比如,目前主流的Selenium Grid就已经提供了官方Docker版本,可以直接以容器的方式建立测试执行环境,也可以很方便地在Pivotal Cloud Foundry和Google Cloud Platform等云计算平台上快速建立测试执行环境。
|
||||
|
||||
基于Docker的Selenium Grid大大减轻了批量虚拟机节点上Web Driver、浏览器版本和守护者进程版本等升级维护的工作量。
|
||||
|
||||
测试开发工程师还可以通过Docker Image的形式,提供某些测试工具,而不是以传统的安装包或者JAR文件的形式,可以实现测试工具开箱即用。
|
||||
|
||||
可见,容器技术已经慢慢渗透到软件研发与运维的各个层面,作为新时代的测试开发工程师,你必须像熟练使用VMware一样,掌握Docker和Kubernetes的原理和使用方法。
|
||||
|
||||
那对于一个测试工程师来说,怎么才能快速具备容器相关知识,并上手涉及容器技术的互联网产品测试呢?
|
||||
|
||||
在这里,我还是要跟你强调选择学习资料时,一定要注意权威性,我给你的推荐依然是[Docker官网的教程](https://docs.docker.com/get-started/),在这里你完全可以理清Docker概念以及具体使用方法,那再结合具体的实战,相信你必定收获颇丰。
|
||||
|
||||
## 云计算技术
|
||||
|
||||
**一方面,很多企业,尤其是互联网企业都在尝试“上云”,** 也就是逐渐把生产环境从原本的集中式数据中心模式转向私有云或者混合云模式。
|
||||
|
||||
前段时间,eBay的一些产品线就对外宣布了和Pivotal Cloud Foundry的合作,会将部分产品线迁移到云端。**显然,作为测试工程师,你必须理解服务在云端部署的技术细节才能更好的完成测试任务。**
|
||||
|
||||
**另一方面,测试基础服务作为提供测试服务的基础设施,比如测试执行环境服务(Test Execution Service)和测试数据准备服务(Test Data Service)等,也在逐渐走向云端。** 比如,国外非常流行的Sauce Labs,就是一个著名的测试执行环境公有云服务。
|
||||
|
||||
一些大型互联网企业,通常还会考虑建立自己的测试执行私有云。最典型的就是,基于Appium + Selenium Grid,搭建移动终端设备的测试执行私有云。
|
||||
|
||||
所以,除了专门进行云计算平台测试的工程师,必须要掌握云计算的知识外,其他互联网产品的测试工程师,也要能够理解并掌握基本的云计算知识和技术。
|
||||
|
||||
在我看来,对于云计算的学习,你的侧重点应该是如何使用云提供的基础设施以及服务。我建议的高效学习方法是,参考你所采用的云方案的官方文档,再结合实际案例进行试用,学习效果会更好。
|
||||
|
||||
你可以尝试用云服务去部署自己的应用,同时还可以结合云平台提供的各类服务(配置服务,数据库服务等)和你的应用做集成。另外,我还建议你尝试用云平台建立自己的小应用集群,体验集群规模的动态收缩与扩展。你还可以尝试在云平台上直接使用Docker部署发布你的服务。
|
||||
|
||||
更进一步,你可以尝试在云端建立自己的Selenium Gird集群,现在Selenium Gird已经发布了对应的Docker版本镜像,你可以非常方便地在云平台上搭建自己的Selenium Grid。
|
||||
|
||||
不要以为这会有多复杂,理解了Docker的基本概念以及对应云平台的使用方法,你就可以在短时间内快速搭建起这样的Selenium集群。
|
||||
|
||||
相信以上这些基本的应用场景,都将更好地帮助你理解云平台的核心功能以及使用场景,从而帮你完成对应产品的测试。
|
||||
|
||||
## DevOps思维
|
||||
|
||||
DevOps 强调的是,开发、测试和运维等组织团队之间,通过高效自动化工具的协作和沟通,来完成软件的全生命周期管理,从而实现更频繁地持续交付高质量的软件,其根本目的是要提升业务的交付能力。
|
||||
|
||||
**DevOps的具体表现形式可以是工具、方法和流水线,但其更深层次的内涵还是在思想方法,以敏捷和精益为核心,通过发现问题,以系统性的方法或者工具来解决问题,从而实现持续改进。**
|
||||
|
||||
因此,测试工程师也必须深入理解DevOps思想的核心和精髓,才能在自动化测试和测试工具平台的实现上做出最契合的设计。无论是测试工程师,还是测试开发工程师,都会成为DevOps实践成功落地的重要推动力。
|
||||
|
||||
要想真正学习和掌握DevOps,并不是简单地学习几款工具的使用,更重要的是需要有DevOps思维,能够将各个工具有机结合,提供高效的CI/CD流水线。
|
||||
|
||||
对于DevOps,我建议的学习路径是,你可以从深入掌握Jenkins之类的工具开始,到熟练应用和组合各种plugin来完成灵活高效的流水线搭建,之后再将更多的工具逐渐集成到流水线中以完成更多的任务。
|
||||
|
||||
相信通过这样的学习,当你再面对相关的测试工作时,必然可以轻松应对。
|
||||
|
||||
## 前端开发技术
|
||||
|
||||
前端开发技术的发展突飞猛进,新的框架与技术层出不穷,Vue.js,Angular和React等让人应接不暇。并且,还有很多在此类框架基础上开发的组件库可以直接使用,比如AntD,大大降低了前端开发的难度和时间成本。
|
||||
|
||||
但是,前端开发技术的发展和测试又有什么关系呢?
|
||||
|
||||
**从测试工程师的角度来讲,如果你能够掌握前端开发技术,也就意味着你可以更高效地做前端的测试,更容易发现潜在缺陷。同时,你还可以自己构建测试页面,来完成各类前端组件的精细化测试,大大提高测试覆盖率和效率。**
|
||||
|
||||
从测试开发工程师的角度来讲,很多测试平台和工具都需要UI界面,比如很多公司内部构建的测试数据服务和测试执行服务,如果你能熟练掌握基本的前端开发技术,那你就可以很方便、高效地构建测试平台和工具的UI。
|
||||
|
||||
关于前端技术的学习路径,通常你首先需要掌握最基本的JavaScript、CSS、JQuery和HTML5等知识,然后再去学习一些主流的前端开发框架,比如Angular.js、Backbone.js等。当然现在的Node.js的生态圈非常发达,你如果能够掌握Node.js,那么很多东西实现起来都可以得心应手。
|
||||
|
||||
我个人推荐从网上下载一些样例代码进行学习,同时学习使用脚手架从无到有去建立自己的前端应用。
|
||||
|
||||
## 总结
|
||||
|
||||
为了应对技术发展趋势,做好软件产品的测试工作,软件测试工程师需要掌握非常多的非测试专业知识,包括:网站架构、容器技术、云计算技术、DevOps思维,以及前端开发技术的核心知识以及实践。
|
||||
|
||||
对于这类新技术的学习,我强烈推荐你直接阅读官方网站的文档以及代码示例。这种方式,可以让你少走弯路,同时保证所学内容是最新的。
|
||||
|
||||
当然,我跟你分享的这些非测试专业知识,只是众多技术的冰山一角,你在实际的测试工作中也会遇到更多的技术,希望你可以举一反三,不断扩充自己的知识面,向着一个优秀测试工程师、架构师努力!
|
||||
|
||||
## 思考题
|
||||
|
||||
你所在的测试领域,还有哪些非测试知识是必须掌握的,这些知识可以从哪些方面帮到你呢?
|
||||
|
||||
欢迎你给我留言。
|
||||
|
||||
|
||||
154
极客时间专栏/软件测试52讲/测试基础知识篇/11 | 互联网产品的测试策略应该如何设计?.md
Normal file
154
极客时间专栏/软件测试52讲/测试基础知识篇/11 | 互联网产品的测试策略应该如何设计?.md
Normal file
@@ -0,0 +1,154 @@
|
||||
<audio id="audio" title="11 | 互联网产品的测试策略应该如何设计?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d8/e4/d8be047a749ea8609bdddbda1ff1f9e4.mp3"></audio>
|
||||
|
||||
在上一篇文章中,我跟你分享了做好互联网产品测试你要具备的非测试知识,那么现在我就来跟你聊聊应该如何设计互联网产品的测试策略。
|
||||
|
||||
在我开始今天的话题之前,请你先思考一下为什么我会把互联网产品的测试策略单独拿出来讨论,互联网产品的测试策略和传统软件产品的测试策略到底有哪些不同?
|
||||
|
||||
## 研发流程的不同决定了测试策略的不同
|
||||
|
||||
如果直接回答互联网产品和传统软件产品的测试策略有何不同,你会有些摸不着头脑,那么按照我一直在强调的知其然知其所以然的原则,你可以先去总结这两类产品的研发本身最大的不同是什么?
|
||||
|
||||
**那就是,互联网产品的“快”。**
|
||||
|
||||
我在专栏前面的文章中,已经提到了互联网产品的上线周期通常是以“天”甚至是以“小时”为单位,而传统软件产品的周期多以“月”,甚至以“年”为单位。
|
||||
|
||||
发布周期的巨大差异决定了,传统软件产品的测试策略必然不适用于互联网产品的测试,二者的测试策略必然在测试执行时间和测试执行环境上有巨大差异。
|
||||
|
||||
比如,对于功能自动化测试用例,执行一轮全回归测试需要12小时,对传统软件来说这根本不是问题,因为发布周期很长,留给测试的时间也会很充裕。
|
||||
|
||||
不要说全回归测试执行时间需要12小时,哪怕是需要几天几夜也没有任何问题,就像我以前在思科(Cisco)做传统软件测试时,一轮完整的全回归测试的GUI测试用例数接近3000个,API测试用例数更是接近25000个,跑完全部用例需要将近60小时。
|
||||
|
||||
但对互联网产品来说,通常24小时就会有一到两次的发布,发布流程通常包含了代码静态扫描、单元测试、编译、打包、上传、下载、部署和测试的全流程。显然留给测试执行的时间就非常有限,传统软件动辄十几个小时的测试执行时间,在互联网产品的测试上,根本行不通。
|
||||
|
||||
**通常情况下,互联网产品要求全回归测试的执行时间不能超过4小时。**
|
||||
|
||||
那么,如何在保证测试质量和测试覆盖率的前提下,有效缩短测试执行时间呢?
|
||||
|
||||
<li>
|
||||
<p>首先,你可以引入测试的并发执行机制,用包含大量测试执行节点的测试执行集群来并发执行测试用例。<br />
|
||||
测试执行集群,你可以简单理解为是一批专门用来并发执行测试用例的机器。常见的测试执行集群,由一个主节点(Master)和若干个子节点(Node)组成。其中,主节点用来分发测试用例到各个子节点,而各个子节点用来具体执行测试用例。<br />
|
||||
目前,很多互联网企业都建立了自己的测试执行集群。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>其次,你必须从测试策略上找到突破口,这也是我今天跟你分享的主题。<br />
|
||||
接下来,我会先简单为你介绍一下传统软件产品的测试策略设计,然后再给你分享互联网产品的测试策略,这样可以通过对传统软件产品测试策略的回顾,加深你对互联网产品测试策略的认识。</p>
|
||||
</li>
|
||||
|
||||
## 传统软件产品的测试策略设计
|
||||
|
||||
传统软件产品的测试策略,通常采用如图1所示的金字塔模型。该金字塔模型是迈克 · 科恩(Mike Cohn)提出的,在很长一段时间内都被认为是测试策略设计的最佳实践。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/54/b4/5456dcb2f274e8e04077ee1251ac4ab4.png" alt="" />
|
||||
|
||||
**第一,单元测试**
|
||||
|
||||
金字塔最底部是单元测试,属于白盒测试的范畴,通常由开发工程师自己完成,由于越早发现缺陷其修复成本越低,所以传统软件产品的测试策略提倡对单元测试的高投入,单元测试这一层通常都会做得比较“厚”。
|
||||
|
||||
另外,传统软件产品,生命周期都比较长,通常会有多个版本持续发布,为了在后期的版本升级过程中能够尽早发现并快速定位问题,每次build过程中都会多次反复执行单元测试,这也从另一个角度反映出单元测试的重要性。
|
||||
|
||||
**第二,API测试**
|
||||
|
||||
**金字塔中间部分是API测试,主要针对的是各模块暴露的接口,通常采用灰盒测试方法。灰盒测试方法是介于白盒测试和黑盒测试之间的一种测试技术,其核心思想是利用测试执行的代码覆盖率来指导测试用例的设计。**
|
||||
|
||||
以API接口测试为例,首先以黑盒方式设计如何调用API的测试用例,同时在测试执行过程中统计代码覆盖率,然后根据代码覆盖率情况来补充更多、更有针对性的测试用例。
|
||||
|
||||
总体来看,API测试用例的数量会少于单元测试,但多于上层的GUI测试。
|
||||
|
||||
**第三,GUI测试**
|
||||
|
||||
金字塔最上层的是GUI测试,也称为端到端(E2E,End-to-end)测试,是最接近软件真实用户使用行为的测试类型。通常是模拟真实用户使用软件的行为,即模拟用户在软件界面上的各种操作,并验证这些操作对应的结果是否正确。
|
||||
|
||||
GUI测试的优点是,能够实际模拟真实用户的行为,直接验证软件的商业价值;缺点是执行的代价比较大,就算是采用GUI自动化测试技术,用例的维护和执行代价依然很大。所以,要尽可能地避免对GUI测试的过度依赖。
|
||||
|
||||
另外,GUI测试的稳定性问题,是长期以来阻碍GUI测试发展的重要原因。即使你采用了很多诸如retry机制以及异常场景恢复机制等方式,GUI测试的随机失败率依旧高居不下。
|
||||
|
||||
## 互联网产品的测试策略设计
|
||||
|
||||
对于互联网产品来说,迈克的金字塔模型已经不再适用,我会通过GUI测试、API测试、单元测试这三个方面,来跟你聊聊互联网产品的测试策略有哪些变化,应该如何设计。
|
||||
|
||||
**第一,GUI测试**
|
||||
|
||||
互联网产品的上线周期,决定了GUI测试不可能大范围开展。
|
||||
|
||||
<li>
|
||||
互联网产品的迭代周期,决定了留给开发GUI自动化测试用例的时间非常有限;
|
||||
</li>
|
||||
<li>
|
||||
<p>互联网产品客户端界面的频繁变化,决定了开展GUI自动化测试的效率会非常低,这也是最糟糕的。<br />
|
||||
因为敏捷模式下的快速反馈,在下一个迭代(sprint)可能就需要根据反馈来做修改和调整客户端界面,那么刚开发完,甚至是还没开发完的GUI自动化测试用例就要跟着一起修改。<br />
|
||||
这种频繁地修改,对开发GUI自动化测试是非常不利的。因为,刚开发完的自动化用例只跑了一次,甚至是一次还没来得及跑就需要更新了,导致GUI自动化测试还不如手工测试的效率高。</p>
|
||||
</li>
|
||||
|
||||
由此,**互联网产品的GUI测试通常采用“手工为主,自动化为辅”的测试策略,手工测试往往利用探索性测试思想,针对新开发或者新修改的界面功能进行测试,而自动化测试的关注点主要放在相对稳定且核心业务的基本功能验证上。所以,GUI的自动化测试往往只覆盖最核心且直接影响主营业务流程的E2E场景。**
|
||||
|
||||
另外,从GUI测试用例的数量来看,传统软件的GUI测试属于重量级的,动不动就有上千个用例,因为传统软件的测试周期很长,测试用例可以轮流排队慢慢执行,时间长点也没关系。
|
||||
|
||||
而互联网产品要求GUI测试是轻量级的,你见过或者听过有哪个互联网产品设计了上千个GUI测试用例吗?互联网产品的上线周期,直接决定了不允许你去执行大量的用例。
|
||||
|
||||
**第二,API测试**
|
||||
|
||||
你现在可能要问,既然互联网产品不适宜做重量级的GUI测试,那么怎样才能保证其质量呢?
|
||||
|
||||
其实,对于互联网产品来说,把测试重点放在API测试上,才是最明智的选择。为什么呢?我给你总结了以下五条原因。
|
||||
|
||||
<li>
|
||||
**API测试用例的开发与调试效率比GUI测试要高得多**,而且测试用例的代码实现比较规范,通常就是准备测试数据,发起request,验证response这几个标准步骤。
|
||||
</li>
|
||||
<li>
|
||||
<p>**API测试用例的执行稳定性远远高于GUI测试。** GUI测试执行的稳定性始终是难题,即使你采用了很多技术手段(这些具体的技术手段,我会在讲解GUI测试时再详细展开),它也无法做到100%的稳定。<br />
|
||||
而API测试天生就没有执行稳定性的问题,因为测试执行过程不依赖于任何界面上的操作,而是直接调用后端API,且调用过程比较标准。</p>
|
||||
</li>
|
||||
<li>
|
||||
单个API测试用例的执行时间往往要比GUI测试短很多。当有大量API测试需要执行时,API测试可以很方便地以并发的方式执行,所以可以在短时间内完成大批量API测试用例的执行。
|
||||
</li>
|
||||
<li>
|
||||
<p>现在很多互联网产品采用了微服务架构,而对微服务的测试,本质上就是对不同的Web Service的测试,也就是API测试。<br />
|
||||
在微服务架构下,客户端应用的实现都是基于对后端微服务的调用,如果做好了每个后端服务的测试,你就会对应用的整体质量有充分的信心。所以,互联网产品的API测试非常重要。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>API接口的改动一般比较少,即使有改动,绝大多数情况下也需要保证后向兼容性(Backward Compatibility)。所谓后向兼容性,最基本的要求就是保证原本的API调用方式维持不变。<br />
|
||||
显然,如果调用方式没有发生变化,那么原本的API测试用例也就不需要做大的改动,这样用例的可重用性就很高,进而可以保证较高的投入产出比(ROI)。</p>
|
||||
</li>
|
||||
|
||||
可见,**互联网产品的这些特性决定了,API测试可以实现良好的投入产出比,因此应该成为互联网产品的测试重点。这也就是为什么互联网产品的测试策略更像是个菱形结构的原因。**
|
||||
|
||||
如图2所示就是这个菱形的测试策略,遵循“**重量级API测试,轻量级GUI测试,轻量级单元测试**”的原则。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c7/cd/c72e5900d670f779c5dd6827407032cd.png" alt="" />
|
||||
|
||||
**第三,单元测试**
|
||||
|
||||
了解了“重量级API测试”和“轻量级GUI测试”,接下来,我就跟你说说为什么是“轻量级单元测试”。
|
||||
|
||||
从理论上讲,无论是传统软件产品还是互联网产品,单元测试都是从源头保证软件质量的重要手段,因此都非常重要。但现实是,互联网产品真正能全面开展单元测试,并严格控制代码覆盖率的企业还是凤毛麟角。
|
||||
|
||||
但凡存在的都会有其合理性,我认为最主要的原因还是在于互联网产品的“快”,快速实现功能,快速寻求用户反馈,快速试错,快速迭代更新。
|
||||
|
||||
在这样的模式下,互联网产品追求的是最快速的功能实现并上线,基本不会给你时间去做全面的单元测试。即使给你预留了单元测试的时间,频繁的迭代也会让单元测试处于不断重写的状态。因此,单元测试原本的价值,很难在实际操作层面得到体现。
|
||||
|
||||
**那么,互联网产品真的可以不用做单元测试么?答案是否定的,只不是这里的单元测试策略要采用“分而治之”的思想。**
|
||||
|
||||
互联网产品通常会分为应用层和后端服务,后端服务又可以进一步细分为应用服务和基础服务。
|
||||
|
||||
后端基础服务和一些公共应用服务相对稳定,而且对于系统全局来说是“牵一发而动全身”,所以后端服务很有必要开展全面的单元测试;而对于变动非常频繁的客户端应用和非公用的后端应用服务,一般很少会去做单元测试。
|
||||
|
||||
另外,对于一些核心算法和关键应用,比如银行网关接口,第三方支付集成接口等,也要做比较全面的单元测试。
|
||||
|
||||
**总结来讲,互联网产品的全面单元测试只会应用在那些相对稳定和最核心的模块和服务上,而应用层或者上层业务服务很少会大规模开展单元测试。**
|
||||
|
||||
## 总结
|
||||
|
||||
传统软件通常采用金字塔模型的测试策略,而现今的互联网产品往往采用菱形模型。菱形模型有以下四个关键点:
|
||||
|
||||
- 以中间层的API测试为重点做全面的测试。
|
||||
- 轻量级的GUI测试,只覆盖最核心直接影响主营业务流程的E2E场景。
|
||||
- 最上层的GUI测试通常利用探索式测试思维,以人工测试的方式发现尽可能多的潜在问题。
|
||||
- 单元测试采用“分而治之”的思想,只对那些相对稳定并且核心的服务和模块开展全面的单元测试,而应用层或者上层业务只会做少量的单元测试。
|
||||
|
||||
## 思考题
|
||||
|
||||
你所在的公司或者产品线,采用的是什么测试策略?看完了本篇文章,你会如何评价你们公司的测试策略呢?有哪些好的地方,又有哪些地方需要改进?
|
||||
|
||||
欢迎你给我留言。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user