关于程序员:阿里研究员软件测试中的18个难题

4次阅读

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

简介: 对于软件测试来说,怎么样才算测够了?如何评估测试的有效性?那么多测试用例,当前怎么删?在软件测试中会遇到十分多的问题,阿里研究员郑子颖分享了 18 个他总结出的难题以及相干认识,心愿对同学们有所启发。


十多年前我在上一家公司的时候看到过外部有个网站有一个 Hard Problems in Test 的列表,下面大略有三四十个问题的样子,是各个部门的测试同学提供的。但惋惜起初那个 list 失传了,我很悔恨本人过后没有保留一份。起初很屡次我都想要找到那份 list,因为下面列的那些问题指出了测试业余在本身专业性上的微小倒退空间。那份 list 上的问题让过后的我置信,软件测试这件事件自身的难度一点都不亚于软件开发,甚至可能更难一点。

如果明天要重建这么一份 Hard Problems in Test 列表,上面这些问题是我会加到这份列表上的 [1]。

一 测试充沛度(Test Sufficiency)

如何答复“测够了吗“(包含测新和测旧)。代码覆盖率是掂量测试充分性的终点,但远远不是起点。要答复”测够了吗“,至多还要思考是否测了所有的场景、所有的状态、所有的状态转移门路、所有的事件序列、所有可能的配置、所有可能的数据等等等等。即便如此,咱们可能还是无奈 100% 确信咱们曾经测够了。可能咱们最终只能做到十分趋近于测够了 [2]。

二 测试有效性(Test Effectiveness)

如何评估一组测试用例的发现 bug 的能力。有效性(发现 bug 的能力)和充分性(测够了没有)是两个正交的属性。评估测试用例有效性能够通过正向的剖析进行,例如,剖析测试用例是否校验了所有在测试过程中 SUT 落库的数据。更具备通用性的做法是变异测试(Mutation Testing),即在被测代码里注入不同的“人造 bug”,统计多少能被测试用例感知到。目前变异测试咱们曾经有工程化规模化的落地了,后续的工作重点有:1)如何避免钝化(或曰“杀虫剂效应”),2)岂但对被测代码进行注入,还能对配置、数据等进行更全面的注入。

三 测试用例瘦身

以前广告行业有句话:我晓得广告费有一半是节约掉的,但不晓得哪一半是节约掉的 [3]。

软件测试也有相似的困惑:那么多用例,要花那么多工夫去跑,我晓得这外面有很多工夫是节约掉的,但我不晓得哪些工夫是节约掉的。节约的模式包含:

  • 冗余步骤:有些是节约在一些反复的步骤上,每个用例都要去做一些相似的数据筹备,每个用例都要去执行一些两头过程(这样能力推动到下一步)。
  • 等价类:一个领取场景,我要不要在所有的国家、所有的币种、所有的商户、所有的领取渠道和卡组的排列组合都测一遍?这么测,代价太高。不这么测,我放心可能某个特定商户在某个特定国家有个特定逻辑我就漏掉了。对于具体的业务,还能够进行人肉剖析。有没有更通用的、而且比拟齐备和牢靠的等价类剖析的技术手段?
  • 我有 N 个用例,我猜这 N 个用例外面可能存在 M 个用例,即便删掉这 M 个用例,剩下的 N - M 个用例的成果和之前 N 个用例的成果一样。如何辨认是否存在这样的 M 个用例、如果存在的话是哪 M 个。

我加入过外部一场品质线降职到 P9 的评审,过后有个评委问了那位同学一个问题:“那么多测试用例,当前你怎么删”。这个问题看似简略,其实十分难。我感觉,从原理上来说,如果测试充沛度和测试有效性的度量都做的十分好了、度量老本非常低了,咱们是能够通过大量的一直的尝试来删用例的。这是一种工程化的思路,兴许还有其余的实践推导的思路。

四 测试分层

很多团队都会纠结到底要不要做全链路回归、做到什么水平。这个问题的外围点就是:有没有可能、有没有一种做法,只有把零碎间的边界约定的足够好足够残缺,就能够做到在改变一个零碎的代码后,不须要和上下游零碎进行集成测试,只有依照边界约定验证好本人的代码就能够确保没有任何 regression 了。

包含我在内的很多人置信那是可能的,但既无奈证实,也不敢在实操中就齐全不跑集成。咱们也不足能够齐全复制的成功经验,不足一套残缺的方法论领导开发团队和 QA 团队要怎么做就能够达到回归无需集成上下游。

有时候,我感觉我当初就像是哥德堡的市民,一直的走啊走,尝试找出一条一次性不反复的走过那 7 座桥的路线。但兴许就有那么一天,有一个像欧拉那样的人会呈现在我背后,用实践证实通知我,那是不可能的。

五 缩小剖析脱漏

剖析脱漏是很多故障的起因。开发做系分的时候,有一个 corner case 没思考到、没有解决。测试做测分的时候,遗记思考某个非凡场景了。兼容性评估,评估下来没有兼容性问题的,但后果是有的。而且很多时候,剖析脱漏属于 unknown unknowns,我压根就不晓得我不晓得。有没有一套办法和技术,能够缩小剖析脱漏,能够把 unknown unknowns 转化为 knowns?

六 用例主动生成

Fuzz Test、Model Based Test、录制回放、Traffic Bifurcation(引流)等都是主动生成用例的伎俩。有些曾经比拟成熟(例如单零碎的录制回放、引流),有些多个团队都在摸索(例如 Fuzz),有些则始终没有大规模的成功实践(例如 MBT)。咱们也有过摸索如何从 PRD 里通过 NLP 来生成用例。用例主动生成中,有时候难点还不是生成 test steps,难度反而是怎么生成 test oracle。Anyway,测试用例主动生成是一个十分大的畛域,这个方向上将来能够做的还十分多。

七 问题主动排查

包含线上和线下。对于比拟高级的问题,主动排查计划往往有两个局限性。首先,计划不够通用,多多少少比拟定制化。其次,比拟依赖人工积攒规定(说的好听点叫“专家教训”),次要是通过记录和反复人肉排查的步骤来实现。然而,每个问题都不齐全一样,问题略微一变,之前的排查步骤可能就不 work 了。当初有一些技术,比方调用链路的主动比对,对排查问题和缺点主动定位很有帮忙。

八 缺点主动修复

阿里的 Precfix、Facebook 的 SapFix 等是目前比拟出名的一些工业界的做法。但总的来说,现有的技术计划,都有这样那样的局限性和有余,这个畛域还在绝对晚期阶段,前面的路还很长。

九 测试数据筹备

测试用例的一个重要设计准则是:测试用例之间不应该有依赖关系,一个测试用例的执行后果不应该受到其余测试用例的执行后果(包含是否执行)的影响。基于这个准则,传统的最佳工夫是确保每个测试用例都应该是自力更生的:一个用例须要触发的后盾解决流程应该由这个用例本人来触发,一个测试用例须要的测试数据应该本人来筹备,等等。但如果每个用例所须要用到的测试数据都是本人来从头筹备的,执行效率就比拟低。怎么既不违反“测试用例之间不应该有依赖关系”的大准则,又能缩小测试数据的筹备工夫?

我构想的是一种更加齐备的数据银行。每个测试用例执行完后,都会把它本人产生的数据交给数据银行,例如,一个在某个特定国家的曾经通过 KYC、曾经绑了一张卡的会员,一笔曾经领取胜利的交易,一个曾经实现入驻签约流程的商户。下一个测试用例开始的时候,会先问一下数据银行:“我要一个满足这样这样条件的商户,你有没有”。上个用例跑进去的那个商户正好符合条件,数据银行就会把商户“借”给这个用例用。而且一旦借出,直到被偿还前,这个商户不会被借给其余用例。

通过一段时间的运行,数据银行可能学习到每个测试用例须要什么样的数据、以及会产生什么样的数据。这个常识是通过学习失去的,不须要人肉去增加形容,所以也能实用于老零碎的存量用例。有了这个常识,数据银行能够实现两个优化:

  • 一次测试执行批次开始后,数据银行会看到这个批次中前面那些用例须要什么样的数据,提前先筹备起来。这样,等执行到那些用例的时候,数据银行里就曾经有符合条件的数据筹备好了。
  • 依据每个测试用例须要什么样的数据、以及会产生什么样的数据,数据银行能够正当的编排测试用例的执行先后秩序,最大化的实现测试数据的复用,缩小测试数据的量和筹备开销。

测试银行把测试数据“借”给用例的时候,能够有多种不同的模式。能够是独占(exclusive)的,也能够是共享的。共享的也能够指定共享读、共享写、还是都只读不能写(例如,一个商户能够被多个用例用来测试下单领取结算场景,但这些用例都不能够去批改这个商户自身,例如从新签约)。

如果把开关、定时工作等 resource 也作为一种狭义的测试数据由数据银行来治理,能实现测试用例尽可能并行执行。例如,有 N 个用例都须要批改一个开关值,这 N 个用例如果并行执行的话就会相互影响,他们相互之间应该串行执行。但 N 个用例中的任何一个,都能够和这 N 个用例之外的用例并行执行。数据银行把握了每个用例对各种资源的应用模式的详细情况,再加上每个用例的均匀运行工夫等数据,就能够最优化、最精确的对一批测试用例进行编排,做到能够并行的都尽可能并行、不能并行的确保不并行,而且还能够在一个批次的执行过程中一直的调整余下还未执行的用例的编排。

这样一个数据银行是广泛实用的,不同业务之间的差别无非是具体的业务对象和 resource 不一样。这些差别能够通过插件模式实现。如果有这么一个通用的数据银行 [4],能够很不便的 adopt,大量的中小软件团队的测试效率都能够失去显著的进步。这样的一个更加齐备的数据银行的想法,我到目前为止还只是想法,始终没有机会实际。

十 异样测试

一个分布式系统,它的外部、外部各局部之间以及它和内部的交互都会呈现各种异样:拜访超时、网络连接和耗时的抖动、连贯断开、DNS 无奈解析、磁盘 /CPU/ 内存 / 连接池等资源耗尽等等。如何确保零碎的行为(包含业务逻辑、以及零碎自保护措施如降级熔断等)在所有的状况下都是合乎预期的?明天咱们的线上演练(实质上也是一种异样测试))曾经做了很多了。如何把更多的问题提前到线下来发现?对于一个简单的分布式系统来说,要遍历所有可能出现异常的中央和所有可能呈现的异样,异样用例的数量是十分大的。此外,某些异常情况下,零碎对外体现进去的行为应该没有变动;而另一些异常情况下,零碎行为是会有变动的。对于后一类,如何给出每一个异样用例的预期后果(即 test oracle),也是比拟有难度的。

十一 并发测试(Concurrency Test)

并发(concurrency)可能呈现在各个 level:数据库层面,对同一张表、同一条记录的并发读写;单零碎层面,同一个过程内的多个线程之间的并发,单服务器上的多个过程之间的并发,以及单个服务的多个实例之间的并发;业务层面,对同一个业务对象(会员、单据、账户等)的并发操作,等等。传统的并发测试是基于性能测试来做的,有点靠撞大运,而且常常是即使跑出问题来了也会被忽视或者无奈 repro。并发测试畛域,我接触过的一些成绩包含 Microsoft 的 CHESS 以及阿里的谭锦发同学在摸索的分布式模型查看 &SST 搜索算法。

十二 回滚的测试

平安生产三板斧宣传了多年,在阿里经济体内大家都能做到“可回滚”了。但我所察看到的是:很多时候咱们有回滚的能力,然而对回滚后零碎的正确性,事先保障的伎俩还不够。咱们更多的是靠灰度和监控等预先伎俩来确保回滚不会回滚出问题来。事实上,过来两年,我本人曾经亲身经历过好几次回滚导致的线上故障。回滚测试的难度在于:须要笼罩的可能性十分多,一个公布可能在任何一个点上回滚。回滚可能还会引发兼容性问题:新代码生成的数据,在新代码被回滚后,老代码是否还能正确的解决这些数据。

十三 兼容性测试

代码和数据的兼容性问题有很多模式。例如,如何确保新代码可能正确的解决所有的老数据?有时候,老数据是几个月前的老代码产生的,例如,一个正向领取单据可能会到几个月当前才产生退款退票。有时候,老数据可能就是几分钟前产生的:用户的一个操作,背地的流程执行到两头的时候代码被降级了。验证这些场景下的兼容性的难度在于:须要验证的可能性太多了。明天的退款申请对应的正向单据,可能是过来很多个版本的代码产生的。一个业务流程执行到两头具体什么中央代码被降级了,可能性也十分多。

异样测试、并发测试、回滚测试、兼容性测试,这些问题的一个共同点是:咱们晓得这些问题是可能存在的,但要测的话,须要测的可能性又太多。

十四 Mock

测试的有效性也依赖于 mock 的正确性。既然是 mock,它和被 mock 的服务(包含外部的、二方的和三方的)的行为就多多少少会有差别。这种差别就有可能导致 bug 被漏过。前人也为此想出了“流量比对”等方法。我已经有另一个想法:“一鸭三吃”。也就是说,通过 bundle 和 compiler instruction 等办法,让同一套源代码反对三种不同的编译构建模式:

  • 失常模式:这就是和明天的编译构建是一样的,产出的构建物是拿去生产环境跑的。
  • Mock 模式:这个模式编译进去的就是该服务的一个 mock,但因为是同一套代码编译进去的,最大可能的保留了原来的业务逻辑,做到最大限度的仿真。而且因为是同一套代码编译进去的,前期也不会有“脱钩”的放心,利用代码里的业务逻辑变动都能及时反映在 mock 里,大大减少 mock 的人肉保护工作量。
  • 压测模式:这个模式编译进去的也是一个 mock,但这个 mock 是用来给(上游)做性能测试用的。过来在线下的性能压测中常常遇到的状况是:咱们想要压的零碎还没到瓶颈,这个零碎的上游零碎(往往是一个测试环境)反而先到瓶颈了。压测模式编译进去的这个 mock 就义了一部分的业务逻辑仿真,但能确保这个 mock 自身性能十分好,不会成为性能瓶颈(但对 lantency 依然是仿真的)。

这个“一鸭三吃”的想法 so far 还停留在想法层面,我还始终没有机会实际一下。

十五 动态代码剖析

有一些类型的问题,要用通常意义上的软件测试来发现,难度和老本很高,但反而是通过动态代码剖析来发现反而比拟容易。例如,ThreadLocal 变量遗记革除,会导致内存溢出、会导致要害信息在不同的不同的上游申请之间串错。另一个例子是 NullPointerException。一种做法是通过 fuzz testing、异样测试等伎俩来裸露代码里的 NPE 缺点,以及能够在执行测试回归的时候察看 log 外面的 NPE。但咱们也能够通过动态代码剖析,更早的就发现代码外面可能存在的 NPE。有一些并发问题也能够通过动态代码剖析来晚期精确发现。总之,咱们心愿尽可能多的通过动态代码剖析来防住问题。

十六 形式化验证(Formal Verificaition)

除了在协定、芯片、要害算法等下面的使用以外,形式化办法在更偏业务的层面是否有使用的价值和可能?

十七 防错设计(Mistake Proof)

严格来说,防错设计并不是 software testing 领域内的。但做测试做久了就发现,有很多 bug、很多故障,如果设计的更好一点,就压根不会产生(因而也就谈不上须要测试了)。去年我总结了一下领取零碎的防错设计,前面心愿能看到在各类软件系统状态下的防错设计准则都能总结进去,另外,最好还能有一些技术化的伎俩来帮忙更好的落地这些防错设计准则,这个难度可能比总结设计准则的难度更高。

十八 可测性(Testability)

尽管目前大部分开发和 QA 同学都晓得“可测性”这么件事件,但对可测性把握的还不够体系化,很多同学感觉可测性就是开接口、加 test hook。或者,还没有很好的了解可测性这个货色落到本人这个畛域(例如领取零碎、私有云、ERP)意味着什么。在需要和零碎设计分析阶段还不能做到很无效很有体系的从可测性角度提出要求,往往要求比拟滞后。我心愿可测性设计能够总结出一系列像程序设计的 DRY、KISS、Composition Over Inheritance、Single Responsibility,Rule of Three 等设计准则,总结出一系列的反模式,甚至呈现像《设计模式》那样的一本专门的著述。

以上就是我会加到 Hard Problems in Test 列表的问题,也是我曾经或打算投入精力解决的问题。

注:

[1] 我工作中还有一些其余的测试难题,那些问题在这里没有列出来,因为那些问题和特定的业务场景或者技术栈的相关度比拟高。还有一些测试畛域的挑战,难度也很高,例如,回归测试达到 99% 以上通过率、骨干开发以及做到通过代码门禁的 code change 就是能够进入公布的,这些也十分有难度,但难度次要是是偏工程的而不是软件测试技术自身。
[2] 测试充沛度的度量和晋升是两个问题。有一种观点认为,测试充沛的度量和晋升其实是一件事件,用同样的算法剖析数据能够进行度量,也能用同样的算法来基于数据进行测试充沛度的晋升。我不批准这个观点。度量和晋升未必是同一个算法。这样的例子十分多了:测试有效性的度量和晋升、运维稳定性的度量和晋升,等等。即使度量和晋升能够用同一种算法,我也心愿能够尽量再找一些其余办法,尽量不要用同一种算法又做度量又做晋升,因为这样容易“闭环”和产生盲区和。
[3] 当然,这句话明天可能不再是那样了,但那是十几年前,那时候的在线广告和大数据还没到明天这个程度。
[4] 具体模式上,这个数据银行无需是一个平台。它不肯定是一个服务,它也不肯定须要有 UI。它能够就是一个 jar 包,它能够就是在测试执行时 launch 的一个独自的过程。

正文完
 0