互联网时代,你还在讨论如何做好软件测试,我们已经在讨论如何“干掉”测试了
记得在半年前,我在 InfoQ 写了一篇“开发要不要自己做测试?怎么做?”的文章,当时引发了很多开发人员以及测试人员的激烈讨论,那篇文章的主旨是介绍如何通过工程效能团队来赋能开发人员自己进行高效率高质量的软件测试,文中介绍了 Google、Facebook 和 eBay 等一线互联网公司正在推行的“没有专职测试,测试工作由开发人员完成(简称去 QE 化)”的全新模式,并从工具链的视角介绍了测试即服务(Test as a Service)的全局架构。
那么今天的这篇文章,我会从另外一个视角再来聊聊互联网领域“干掉”测试这个话题,只不过这次要讨论的话题会更尖锐、更敏感,不再是“干掉专职测试人员,让开发人员自己做测试”,而是尽量“偷工减料”少做测试,甚至压根不做测试的话题。哇塞,这听起来是不是有点离经叛道,有违大众长久以来的价值观。别急,且听我慢慢道来。
软件测试的本质目的
首先,我们来看一下软件测试的本质目的是什么,你可能会不假思索地告诉我说是为了保证软件产品的质量。那我会继续问你,保证软件产品质量的目的又是什么,你可能稍加思索后会说是为了让大部分软件用户满意,给用户递交价值。完全正确,其实这才是软件测试的本质目的所在。这就有点像你去问一个女生你为什么不吃饭,她会告诉你她的目的是减肥,但仔细一想,减肥真的是不吃饭的目的吗,其实真正的目的应该是“变得更漂亮”,明白了这层利害关系后,你会发现为了“变得更漂亮”的方法就有很多选择了,不吃饭减肥只是其中之一。
恰好“够用”的质量
那么怎么才能使用户对软件满意呢?这里有两种截然不同的思路,一个是通过海量的测试尽最大努力保证软件中没有任何缺陷,由于测试本身的不可穷尽性,显然这种做法的代价是及其巨大的;另一种思路是只测试用户所有可能会使用的软件功能,其他的都不测试,也就是说只提供一个恰好能够满足用户需求的软件产品,这种情况下,软件的研发代价是比较低的。
对于企业,如果产品本身不是与人生命息息相关或者金融相关的,你觉得企业会选择哪个思路?
从经济学的角度出发,一定是后者。也就是说一个“好的”软件产品并不是一个完完全全无任何缺陷的产品,而是一个其质量属性刚好满足用户需求的产品,也就是说不追求完美的质量,而是追求恰好“够用”的质量,恰好够用的质量的另一种表述是软件产品在用户使用的整个生命周期中不会发生问题,或者即使发生问题,但带来风险和损失也足够小,但并不是说软件本身没有任何问题。企业从经济学的角度来讲需要以科学的手段来寻求缺陷风险和研发成本之间的动态平衡。
基于风险驱动的“偷工减料”测试策略
鉴于上述的思想的驱动,现在很多互联网产品在确定测试范围的时候,通常会通过日志数据来分析软件系统的历史使用行为,并根据功能的使用频度来决定测试的优先级,那些被大量使用的功能一定会全面测试,而那些不经常使用的功能只会做基本的测试,而那些很少使用的功能在进度压力较大的情况下就会放弃测试。这就是所谓的基于风险驱动的测试策略。
用户使用频度越高,说明这部分功能更能够为用户带来价值,当这部分功能出问题的时候,产生的风险也就越大,那么这部分功能无疑就是需要重点测试的对象。而那些很少使用的软件功能或者场景,就成为了测试中“偷工减料”的对象。需要强调的是,这里的偷工减料是打了引号的,不是说真的偷工减料,而是指在有足够数据支持的前提下以风险驱动的方式合理缩小测试范围。
基于风险驱动的测试策略在微服务测试中的应用
如果我们将基于风险驱动的测试策略应用与微服务架构的测试,就形成了基于消费者契约的 API 测试策略,而这一测试策略的核心思想就是强调只测试那些有实际使用场景的 API 调用,如果没有使用场景的 API 调用就可以压根不做测试。
为了让你更好地理解这种目前微服务架构下非常行之有效的测试策略,我画了下面的图
图中的 Service A、Service B 和 Service T 是一个系统中的三个微服务,其中 Service T 是被测试对象,可见 Service T 的消费者(也就是使用者)一共有两个,分别是 Service A 和 Service B。如果按照传统的 API 测试策略,当我们需要测试 Service T 的时候,我们就需要找到所有可能的参数组合依次来对 Service T 进行调用,同时结合 Service T 的代码覆盖率来进一步补充遗漏的测试用例,这样做就会导致测试用例数量很多,工作量过大。那么基于风险驱动的消费者契约 API 测试是怎么做的呢?
首先 Service T 作为服务的提供者,它的使用者在这里是确定的,就只有 Service A 和 Service B,如果能够把 Service A 和 Service B 对 Service T 的所有可能的调用方式都测试到,那么就一定可以保证 Service T 的质量,即使如果存在某些 Service T 的其他调用会有问题,那也不会影响整个系统的功能,原因是系统中没有其他 Service 会以可能出错的调用方式来调用 Service T。这样就以“偷工减料”的方式减少了测试范围,但同时又能够保证软件的质量,可见,这种方式在工程实践中是具有很大实用价值的。至于如何才能有效抓取 Service T 的完整契约,可以参考我的新书《测试工程师全栈技术进阶与实践》第五章中“微服务模式下 API 测试”的内容,技术细节就不在这里展开了。
不做测试直接上线的实际案例
上面我们讲了互联网产品测试过程中如何“少做”测试的实际例子,那么接下来我们再来看看互联网产品测试中更极端的场景:如何不做测试或者只做很少测试的实际案例。什么,不做测试就敢上线发布,如果你是工作在传统软件企业的工程师,这听起来简直就是天方夜谭,完全是想“删库跑路”的节奏啊!但是在互联网企业,这种极端的做法还是有其存在价值的,最开始的时候,这种不测试或者只做很少量测试就立马上线发布的做法是被竞争对手逼出来的,后来却渐渐发现这种方式也有其可取之处,尤其是想以最快的方式发布新功能的时候。
我们来试想一个商业竞争的场景,假定某电商网站 A 发布了为期三天的家电促销活动,并为此上线了新的活动页面以及相关优惠券的功能,那么另外一个电商网站 B 为了与其竞争(网站 B 如果不参与这个竞争,那么这段时间以及后续一段时间内的流量很可能就被网站 A 抢走了),就必须在很短的时间内设计开发并上线能够与网站 A 相竞争的类似优惠功能。由于网站 A 是有备而来,所以网站 A 的功能在上线发布之前已经做了充分的测试,但是网站 B 就很被动了,网站 B 必须在很短的时间内开发出类似的优惠活动功能,如果开发完成后的测试还需要占用较多的时间,那么在商业上就会更被动。
为此,网站 B 会在这种逼不得已的情况下,采用不测试或者只做很少的测试就上线发布的策略,但这个发布的过程会采用灰度发布策略来降低未经充分测试的软件直接上线的风险。具体的做法是这样的:
-
从应用服务器集群中选择任意一台服务器 (比如图 2 中的 Node N),然后从负载均衡器中将其注销。
-
然后将这台服务器的软件升级到最新的未经过测试的软件版本。
-
接着,将这台服务器注册到负载均衡器中,并且通过配置负载均衡器,只让这台具有新软件版本的服务器接收万分之一的流量。也就是说,只有很少一部分的用户会访问到新的软件版本。
-
在接下来大概 10-30 分钟的时间里,密切监控这台具有新软件的服务器各项指标以及后台的业务日志。这段时间,相当于在使用真实的用户行为在对新的软件版本进行测试。
-
如果服务器各项指标正常,后台日志也没有监测到任何异常,那么说明新的软件版本没有问题,接下来就可以部署更多的服务器。如果发现有问题,那么此时就需要立即回滚有问题的新软件版本,但是幸运的是,即使发现有问题,实际受影响的用户也是极少数,同时还准确找到了软件的缺陷。
-
如果没问题,就会将新的软件版本部署到更多的服务器上,直到应用服务器集群中所有服务器都部署了新版本。
总结一下,上述过程实际上利用了灰度发布,在风险可控的情况下,直接利用了真实的用户流量来对新的软件版本进行了测试,因此省去了研发阶段的测试工作量,并且加速了发布的速度。这个就是不做测试的一个极端案例。
由上面的两个例子我们可以发现,在那种场景下采用那种测试策略,完全取决于“发生问题时带来的风险(经济损失)”,“后期问题暴露后修复问题的代价”以及“前期研发过程中投入的测试成本”三者之间的博弈。同时,产品架构设计(微服务或者服务网格等)和 DevOps 实践(灰度发布等)也为测试策略的最优选取提供了更多的可能性。
另外,除了测试策略这种从源头上的优化,在测试设计和执行层面还有很多我们需要面对问题,并在此基础上又很多值得优化的内容以及执行方法上的微创新,其中涉及测试执行环境准备的最佳实践,测试数据准备的最佳实践,从源头保证软件质量的代码级测试,以及性能测试与调优的最佳实践,如果你对这一系列的主题感兴趣,可以关注我的新书《测试工程师全栈技术进阶与实践》,必定让你有所收获。