一、异常测试
一个分布式系统,它的内部、内部各部分之间以及它和外部的交互都会出现各种异常:访问超时、网络连接和耗时的抖动、连接断开、DNS无法解析、磁盘/CPU/内存/连接池等资源耗尽等等。如何确保系统的行为(包括业务逻辑、以及系统自保护措施如降级熔断等)在所有的情况下都是符合预期的?今天我们的线上演练(本质上也是一种异常测试))已经做了很多了。如何把更多的问题提前到线下来发现?对于一个复杂的分布式系统来说,要遍历所有可能出现异常的地方和所有可能出现的异常,异常用例的数量是非常大的。此外,某些异常情况下,系统对外表现出来的行为应该没有变化;而另一些异常情况下,系统行为是会有变化的。对于后一类,如何给出每一个异常用例的预期结果(即test oracle),也是比较有难度的。
二、并发测试
并发(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。有一些并发问题也可以通过静态代码分析来早期准确发现。总之,我们希望尽可能多的通过静态代码分析来防住问题。
七、形式化验证
除了在协议、芯片、关键算法等上面的运用以外,形式化方法在更偏业务的层面是否有运用的价值和可能?
八、防错设计
严格来说,防错设计并不是software testing范畴内的。但做测试做久了就发现,有很多bug、很多故障,如果设计的更好一点,就压根不会发生(因此也就谈不上需要测试了)。去年我总结了一下支付系统的防错设计,后面希望能看到在各类软件系统形态下的防错设计原则都能总结出来,另外,最好还能有一些技术化的手段来帮助更好的落地这些防错设计原则,这个难度可能比总结设计原则的难度更高。
九、可测性
虽然目前大部分开发和QA同学都知道“可测性”这么件事情,但对可测性把握的还不够体系化,很多同学觉得可测性就是开接口、加test hook。或者,还没有很好的理解可测性这个东西落到自己这个领域(例如支付系统、公有云、ERP)意味着什么。在需求和系统设计分析阶段还不能做到很有效很有体系的从可测性角度提出要求,往往要求比较滞后。我希望可测性设计可以总结出一系列像程序设计的DRY、KISS、Composition Over Inheritance、Single Responsibility,Rule of Three等设计原则,总结出一系列的反模式,甚至出现像《设计模式》那样的一本专门的著作。
以上就是我会加到Hard Problems in Test列表的问题,也是我已经或打算投入精力解决的问题。
写在最后:
这世界很公平,你想要最好,就一定会给你最痛。能闯过去,你就是赢家,闯不过去,那就乖乖做普通人。所谓成功,并不是看你有多聪明,也不是出卖自己,而是看你能否笑着渡过难关。
所以朋友们,当你遇到困难的时候,不要怕,相信自己,相信最后的你一定可以开出一朵属于自己的花儿来。