一、测试团队的发展之路
1.1、测试团队面临的困局
测试团队面临着不少的苦难,其中具有普遍性的有:
-
测试自动化难:
- 自动化测试程序编写成本高、执行时间长、维护成本高。
- 测试的”技术债“在业务高速发展的过程中越积越多:一边修复老的用例,另一边出现新的失败的用例;一边补充自动化,另一边又因为项目的时间压力遗留了未自动化的用例
-
测试结果的噪声大:
- 回归测试的通过率比较低,每次回归测试都需要排查大量的失败用例,大部分失败用例由于测试环境相互干扰,而不是中间代码导致的,测试人员难以获得成就感,另外,测试结果的噪声多次导致回归测试已经发现的问题在排查中被漏测,变成线上问题。
-
测试不充分:
- 测试分析和用例枚举非常依赖测试人员的经验和业务领域知识,新入行的测试人员很容易出现测试遗漏,同时,缺少有效的技术手段度量和提升测试的充分性。
-
测试人员对自身成长的焦虑:
- 没有时间上和精力上提升自身能力,缺少知识沉淀。
1.2、建立代码门禁
代码门禁:
- 做法大体做法:在公共分支(包括项目分支、迭代分支、主干分支、紧急发布分支)禁用git push,只允许通过pull request来提交代码。
- 我们的研发平台对每个提交到公共分支的pull request都会执行各项校验,包括编译、单元测试和接口测试级别功能测试、静态代码扫描。只有通过这些校验,pull request才能被合并。
也就是说,代码门禁就是简单的把持续集成迁移:从代码提交后(post-submit)提前到了代码提交前(pre-submit)。这简单的前移,彻底改变了我们的研发模式:从以前的”先欠债再还“,变成了现在的”每个代码提交都不能欠债“。
1.3、提升测试的稳定性
自动化测试稳定性差带来的后果是:
- 失败用例的排查工作量非常大
- 对自动化测试丧失信任和信心。如果发现大部分失败用例都是非代码bug原因,有些人就不会重视,只对失败的用例草草看一眼,就说这个是环境问题,不再去排查了,这样依赖,很多正真的bug就被漏掉了。
- 更多的问题被掩盖了。
1.3.1 高频
-
持续打包:
- 团队过去只在部署测试环境前才打包,经常因为打包问题导致部署花费很多时间,影响后面的测试进度。针对这个问题,我们做了持续打包,每隔半个小时或者一个小时对master打包,一旦遇到问题,马上修复。
-
发布:
- 如果在发布过程中问题很多,那么可以尝试更高频的发布。如果原来每周发布一次,就改成每天发布一次,除了原来每周一次的发布,一周里的其他日子,就算没有新代码合并进来,也要“空转”发布一次。这样做的目的是高频来暴露问题,倒逼发布过程中各环节的优化。
-
容灾演练:
-
缩短反射弧:
-
如果一个团队进行功能回归测试的频率是一天一次(例如,每天早上6点),那么星期一白天做的代码改动和Bug修复的测试结果要星期二才能看到。更糟糕的是,如果星期二的测试执行结果显示星期一的代码有问题,那么即便星期二当天就能修复这些问题,修复的效果也要等到星期三才能看到。这样以“天”计的节奏是不符合敏捷开发和业务快速发展要求的。我们希望的节奏是:早上10点改的代码,午饭前就能看到功能测试结果;根据测试结果,做相应的改动,下午就能通过功能回归测试了。
-
变主动验证为“消极等待”,减少测试人员的工作量:
- 过去,开发人员修复了一个失败用例背后的代码问题,测试人员要手动触发这个用例,检查是否通过,当 Bug数量较多时,测试人员工作量较大。有了高频的功能回归后,功能回归用例集每小时都执行一遍最新的代码。当开发人员提交了Bug修复后,测试人员不再需要手动触发用例,而只要“消极”地等着看下一个小时的测试结果。
-
识别和确认小概率问题:
- 当一天只有一次测试时,如果开发人员说一个用例失败的原因是数据库抖动,那么我们也许能接受。但当每小时都有一次测试,连续三次都得出同样的失败结果时,数据库抖动的理由就说不通了,可以推动开发人员进行更深入的排查。
-
暴露基建层的不稳定因素:
-
倒逼人工环节自动化:
- 如果一个团队进行功能回归的频率是一天一次,那么其中的一些人工步骤就可能被容忍。例如,敲一个命令、编辑一个配置文件、设置几个参数、点几下按钮。这些手动步骤本身很可能出错,导致测试通过率下降。用高频的手段,把功能回归的频率从一天一次提高到一小时一次,工程师可能藏无法容忍这些手动步骤了,就有动力把这些步骤改为自动化实现,从而也减少人工出错的可能。
1.3.2 隔离
隔离指不同的测试活动(包括不同的测试运行批次)之间要尽量做到不相互影响。在有些团队里,自动化的功能回归测试和其他日常测试(例如探索性测试)都在同一个测试环境里进行,相互干扰非常大。
相比“三斧子半”里的其他几个(高频、用完即抛、不自动重跑),隔离的重要性是比较容易理解并被广为接受的。隔离的好处有如下两点。
- 减少噪声:减少多个测试批次运行时彼此影响导致的用例失败。
- 提高效率:会产生全局性影响的测试,可以在多个隔离的环境里并发执行
测试隔离的具体策略要根据技术栈、架构、业务形态来具体分析,选择最合适的技术方案。测试隔离一般可以分为硬隔离和软隔离两种。硬隔离和软隔离之间并没有一个清晰的界限:硬隔离偏属物理层,一般通过多实例实现,例如,消息队列和数据库的多个物理实例:软隔离偏属逻辑层,一般通过多租户(Multi-tenancy)和路由(Routing)实现,例如同一消息队列里的不同命名空间、同一数据表里标志位的不同取值、API 调用和消息体上的流量标志位等。
1.3.3 用完即抛
在团队中一直倡导的一个测试设计原则是“测试环境是短暂的”。一个长期存在的测试环境不可避免地会出现各种问题,如脏数据、累积的测试数据未清理、配置的漂移等。这些问题都会严重影响测试的稳定性。虽然可以通过数据清理、配置巡检等手段解决这些问题,但彻底的解决手段是每次都按需创建新的测试环境,用完后即销毁。
测试环境用完即抛的好处如下:
- 解决环境问题,减少脏数据
- 提高可重复性,确保每次测试运行的环境都是一致的
- 倒逼各种优化和自动化能力的建设(测试环境的准备、造数据等)
- 提高资源使用的流动性。在实际的物理资源不变的前提下,增加流动性就能增实际流量。
(1)我们的测试环境搭建能力要很强。搭建新测试环境要快速、可重复、成功率高、无须人工干预,要能可靠地验证搭建出来的新环境是不是好的、是否满足后续测试的要求。
(2)我们的测试策略和自动化的设计必须不依赖一个长期存在的测试环境。例如,不依赖一个长期环境里的老数据进行数据兼容性测试。
(3)日志要打印好。测试环境一旦被销毁,保留下来的就只有应用代码打印的日志(一般保存在 ELK、SLS 等日志服务中)和测试自动打印的测试输出。这些日志和输出要详细清楚,目标是绝大多数的测试用例失败都能通过分析日志定位到问题。我们经常说的一个原则是写日志的时候,要假设以后“日志是你唯一的问题排査手段”。
1.3.4 不自动重跑
很多团队都会在他们的测试平台里增加一个自动重跑的功能:在一次完整的测试执行后,对其中失败的用例自动重跑一遍。如果一个用例的重跑通过,就把这个用例的结果设为通过。
这个做法是不好的,自动重跑虽然在短期内能(在表面上)提升测试(结果)的稳定性,但长期来看,对整个团队、质量、测试自动化都是有害的。
- 自动重跑会使得工程师不再深入地排査问题、揪出隐藏很深的 Bug。因为工程师的时间都是有限的,既然一个用例已经被标为“通过”了,即便只是重跑才通过的,那么工程师也没有必要仔细看。这样就可能漏过一些真正的Bug。在这类“第一次会失败,重跑会通过”的问题里。
- 有一些用例不稳定的根本原因是系统的可测性问题。**有了自动重跑以后,反正用例重跑一下还是能通过的,所以工程师就没有动力改进系统的可测性了,
- 由于前面两点,深层的 Bug没有被揪出来、本质性的问题(例如可测性)没有解决,问题慢慢地积累、蔓延,测试运行后需要重跑的用例越来越多,导致测试执行所需要的总时间越来越长。
- 对于“确定型”的被测系统(例如支付、电商、云计算等)米说,系统行为是确定的因此测试的结果也应该是确定的。对被测系统来说,关闭自动重跑,一开始可能比较痛苦,失败的用例数会很多,但只要坚持下去,梅花香自苦寒来。
1.4、提升测试的充分性
我们可以从两个角度来提升测试充分性和解决“不知道自己不知道”问题。
- 用例自动生成:用技术手段减少测试人员的个人能力差异的影响,减少对个人经验的依赖。
- 业务覆盖率度量:用技术手段发现测试遗漏,为补充测试用例、提升测试充分性指明方向。
1.4.1 用例自动生成
对于人工枚举测试用例来说,一方面人力的多少和水平的高低限制了测试用例的数目和质量。另一方面人工无法穷举所有的输入作为测试用例,覆盖到所有可能的业务场景。为了解决这些问题,用例自动生成技术得到越来越广泛和使用、用例自动生成的重要作用是减少漏测、提效、节省人力成本。
1.4.2 业务覆盖率度量
如果测试可以覆盖100%的代码(包括行覆盖、分支覆盖、条件覆盖等),是不是就测全了,不会遗漏缺陷了? 答案显然是“不”。以下面的代码为例:
public string Format(int month, int year)
{
return string.Format("{0}/{1}",month, year);
}
这段代码要做的事情是把月和年转化为信用卡的过期时间字符串(例如,06/2020)。信用卡过期日的格式是 MM/YYYY,但对于 2020年6月,这段代码返回的是错误的 6/2020。正确的写法应该是
public string Format(int month, int year)
{
return string.Format("{0:00}/{1:0000}",month,year);
}
设计这段代码的测试用例时,如果只测了某些业务场景(例如11/2020),就无法发现这个 Bug。
因此,我们除了度量测试的代码覆盖率,还必须关注业务场景的覆盖率。
业务场景覆盖率的定义和业务形态相关度很大。信用卡过期时间转换的例子比较简单,在实际工作中有各种Sting 类型或者复杂数据类型的参数,其取值和值的变化代表了业务对象的状态、状态的转移路径、事件序列、配置项的可能取值和取值组合、错误码和返回码等,有强业务语义并代表了不同的业务场景,需要从业务场景覆盖率角度去度量和覆盖。