分布式系统的测试是一个比较大的话题,在这里,我们仅用几个阿里云飞天分布式系统测试中比较有特点的实践方式来阐述一下我们对分布式系统测试的理解,希望对大家有所帮助。
1. 阿里云的分布式系统
飞天是阿里云独立开发的大规模分布式计算与存储系统,兼有分布式存储和分布式计算的多重功能。基于飞天大规模分布式系统,我们开发了弹性计算,海量邮箱服务,Key-Value 存储引擎,结构化数据存储引擎和海量数据处理服务等一系列的上层服务,并且基于这些上层服务,我们运营了了 Open Table Service 和 Open Storage Service 等多项开放服务。
2. 测试的层次结构
按照层次和功能,测试可以分为单元测试,功能测试,系统测试,集成测试,端到端测试等若干种测试。这里首先要提的是单元测试,单元测试更像是一种预防代码错误的手段,而不是检测代码错误的手段,我们用覆盖率(包括行覆盖和分支覆盖)来检验单元测试的质量。当然我们也都知道这些指标不能作为单元测试好坏的标准,所以,在项目中,我们也非常注重代码审查,其中包括了单元测试代码的审查。
传统的一般来说,一个产品一般只有对外接口进行功能测试和系统测试,但是由于飞天的代码非常的复杂,模块非常多,每个模块都具有了一个产品的复杂度。所以,我们在模块层面也会进行功能测试和系统测试,为的是保证系统质量能够得到回归,在有代码修改的时候,影响上层业务使用的接口或者隐含逻辑一旦发生变化,能够第一时间知晓。功能测试非常注重对接口函数的覆盖,并且会对各种参数的边界进行复杂的探测,而系统测试比较注重稳定性,一致性,性能甚至容灾能力等各个方面。
在飞天系统内部,有专门的集成测试部门,集成测试团队有一个非常大的责任是通过回归测试用例来保证系统中的各个模块,在版本发布时在一起能够很好的工作,并对之前每个模块定下的质量验收标准进行验证。一旦发现重大风险,集成测试部门有权力不发布其中某些 Feature,如果这个 Feature 有某种重要的商业用途,则进行推迟整体版本的发布。这一点可能和传统软件不太一样。当分布式软件已经比较成熟的时候,底层很多模块的 Feature 在某个版本的删减,大多数时候不会影响到用户的正常使用,但是如果由于这个 Feature 的发布导致分布式系统出现不稳定或者性能大幅下降,则得不偿失。
端到端测试在内部有个简洁的名字叫做 E2E Testing。E2E Testing 的责任人需要负责向上层使用这个模块或者系统的用户方了解具体的需求,这个需求不仅是一个对接口功能的需求,还包含了数据量的需求,机器规模的需求,吞吐率的需求,延迟时间的需求以及业务量在一天或者一周内的曲线等等信息,然后设计 E2E 的测试程序,能够尽可能的模拟真实的数据情况。在本身上层业务就已经有数据的情况下,还可以通过导入真实业务的数据进行测试。通过长时间的 E2E 测试,验证各种指标。
一般来说,我们只有在通过了最后的 E2E 测试之后才能发布版本,上线让应用使用我们的分布式系统。
3. 注重探索式测试
分布式系统的测试非常的复杂,你既无法通过穷举来罗列所有的情况,也不能简单的对接口和性能进行回归来证明一个版本的合格。因为对于分布式系统,不同的数据量,不同的压力,不同的机器规模,有可能在代码里面走的路径完全不一样。而且,由于在大的压力下,磁盘,内存和网络有可能成为系统的瓶颈。更有甚者,在压力不均匀的情况下,单台机器可能会成为整个系统的瓶颈。所以,我们是没有办法设计这么多的测试用例来分别覆盖所有的这些分支情况的。
我们有详细的监控系统可以监控整个集群的各种参数,这些参数不光是硬件参数,更多的是软件本身通过调用我们监控系统的 API 来完成对自身某些指标的统计。这些统计不光在线上系统能够起到监控的作用,执行测试的人员可以通过不断改变测试的各项参数,结合这些指标的变化进行探索式的测试。通常,通过指标在某些压力变化下或者随着时间推移时的异常行为,测试人员会更容易找到一些深藏的 Bug。另外,在系统级测试时,通过对系统内部 Error Log 的监控也能达到很好的效果。
4. 灰盒测试是分布式测试的最有效方法
上面讲到了探索式的测试十分重要,但是光有探索,如果测试人员本身不了解系统的一些内部逻辑,会出现两种情况:第一种是,只验证设计好的场景,其他一些异常的情况,自己无法解释,但是本身又不是验证标准,导致很多隐藏的问题最终在线上爆发;第二种是,一个没头的苍蝇,漫无目的的进行探索,最终浪费了时间,却达不到好的效果。在飞天分布式系统测试中,我们要求测试人员必须从方案设计之初甚至是讨论需求的时候就和开发的同学在一起讨论,测试的同学需要比开发更加理解系统的设计原则。
有一个很典型的例子是,早期,我们内部开发一个基于表结构的存储引擎时,曾经出现过这样一件事情:测试程序在最终验证一致性时,一直都是通过的,但是业务方和我们一起做 E2E 测试的时候,会有很低的概率发现数据读出来是错误的。测试人员百思不得其解,最后发现,这份数据在写入的时候,会先在三个地方进行修改,但是由于一些时序和锁的问题,在改过了两个地方之后就返回成功了,第三个地方是在内存中,过一阵子就会被重新刷成正确的值。如果当时测试的同学知道系统里面这些设计,当时就会设计写入过程中,对数据一致性的检测,不会在用户场景中再发现问题,解决的效率也会因此而提高。
5. 带压力和随机故障模拟的长时间稳定性测试
分布式系统往往是在多台机器上运行多个进程,每个进程都是一个独立主体,运行着自己独立的逻辑,各个进程都有可能因为软硬件故障导致逻辑执行异常。分布式系统要保证的是任何局部的进程异常都不会影响系统的可用性,因此必须要做必要故障模拟和恢复测试。由于进程之间通讯协议的复杂,故障类型的多样,并不能够模拟所有可能出现故障和进程状态的组合,也就是说这样的组合是爆炸的。
在飞天实际的测试过程中,我们采用的是带背景压力和随机故障模拟的长时间稳定性测试。这也是飞天主版本发布的最后一道关。背景压力主要是各个主要模块的读写压力,还有一些诸如 CPU,Memory,Network 的资源消耗器,以模拟机器资源紧张场景。故障模拟操作又比如磁盘错误,包括坏盘,只读等,机器宕机,重启,断网,主要模块进程重启,假死等,这些操作都会按照预先设定比例进行随机组合;在这样的背景压力和故障操作下,长时间(至少 7x24 小时)持续运行上层应用模拟程序 / 作业。同时通过在线监控工具来检查系统是否正常。飞天很多重要的 bug 都是通过这个测试被发现的。当然这类测试也有短处,就是问题调查需要较长的时间,要求测试人员对系统有较深的了解和诊断能力。
6. 如何保障测试的质量
单元测试虽然可以用 Coverage 工具来检验行覆盖和分支覆盖,但是功能测试和系统测试在运行时,进程运行在上百上千台机器上,很难通过既有的软件来搜集到 coverage 数据。我们内部开发了一个叫做 Log Coverage 的工具,能够通过程序在运行过程中输出的 Log 信息的多少来判断测试是否有足够的覆盖率。通过拿到生产线上的 Log 与测试中的 Log 进行比较,会找到我们之前没有测试到的地方。另外,通过对程序中从来没有打印出的 Error Log 的检查,我们也可以知道有多少异常逻辑我们没有测试过。
感谢贾国清对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。
评论