2.2 微服务中的测试策略
作为最早系统性采用微服务架构的公司之一的Thoughtworks,一开始就在开发微服务系统中采取了敏捷模式,开发团队坚持2 Pizza的团队规模,每个团队有1~2名测试人员(Thoughtworks内部叫质量分析师,与传统测试人员略有区别,兴趣的读者可以去官网查看招聘要求)。微服务系统的测试与上述敏捷测试有很多地方是相同的,只是由于被测微服务系统架构的特殊性,会有一些特殊的应对策略。由于微服务实现方式不止一种,因此为了便于讲解,本文所描述的微服务间通信采用的是基于HTTP的RESTful API,实现框架为Spring Boot。下面我们基于敏捷中的测试象限和测试金字塔来看微服务中的测试策略。
2.2.1 测试象限
首先,我们借鉴测试象限(矩阵)中的4个不同的象限作为微服务的测试象限。
其次,我们依照这4个领域思考一下微服务中应该有哪些测试?
最后,我们把它们合起来放在象限里。
厘清思路后,我们一个象限一个象限地看。
1. 面向技术、支持开发的测试
在Lisa的版本中,第一象限里只有单元测试和组件测试。对微服务而言,一个可部署的组件通常为单个微服务,它里面不仅仅包含一些类和方法,还包括了数据库和网络组件,因此除了单个服务的组件测试外,还应该包含一个服务内数据库以及网络组件集成测试,如图2-7所示。
图2-7 微服务中的单元测试、集成测试、组件测试(来源:martinfowler.com)
除此之外,还有其他用来支持开发工作的测试吗?
要回答这个问题,我们就需要回想测试象限最初的版本,Brian的测试矩阵。第一象限的本质是支持开发。
此时,大家脑子里可能会想到“契约测试”,更准确地说,在微服务的上下文里,它通常指的是消费者驱动的契约测试,更详细的契约测试相关内容会在后续章节中讲解。消费者驱动的契约测试所测试的是系统间API交互的规格(准确来说叫契约,这里叫规格是为了好理解),这显然属于软件的具体实现,与业务无关。更重要的是,它通过定义生产者服务的API引导开发。在生产者服务的API定义发生变化时,它又是代码行为变化检查器(请注意是原有行为发生变化,不是代码变化,因为重构也是代码变化,但是重构不改变代码行为)。
所以,契约测试应属于第一象限。
除此之外,代码静态分析技术越来越成熟,甚至可以在IDE中实时提醒开发人员要进行的代码质量测试,也应该属于这个象限。
由于微服务是分布式的,服务本身是无状态的,一旦出现问题,问题定位对于开发人员来说非常棘手,因此APM与日志分析对于好的微服务系统必不可少。它们可以加快开发人员进行问题定位的速度,同时有助于提前发现潜在问题。那么APM和日志分析在笔者看来同样是面向技术支持开发的部分。有人会说APM和日志分析不是测试,但实际上我们只能说这不是传统意义上的测试,从第一象限本身的定位而言,它本身所包含的范围要远大于测试(见Lisa的Agile Testing)。
2. 面向业务、支持团队的测试
笔者认为这个象限内的测试实际上都属于用户故事测试。前面笔者已经讲到了用户故事测试包含哪些内容,这里不再赘述。
这里需要特别提到的是,对于微服务而言,实例化需求是非常重要的。只有需求实例化,才能减少沟通带来的理解偏差,有了明确的需求实例才能引导团队实现业务并快速且准确地发布。
总体而言,这一个阶段的测试在微服务化场景下会做得较为轻量,以适应快速迭代开发的需求。
3. 面向业务、评价产品的测试
探索性测试、用户体验测试、用户验收测试都是常见的评价产品的测试。此外,线上用户的使用反馈也是评估产品的一种好方式。
微服务场景下每个服务都可以独立开发、测试、部署、运维,加上云技术的加持,微服务下评价产品的方式更加多样,并且成本更低。例如金丝雀测试、A/B测试等方式能够让部分用户更早地看到新产品或者看到同一产品的不同形态,再通过对用户的行为进行监控,例如监控用户的页面点击行为、页面停留时间等,去更准确地评估用户对产品的反馈。
不过,是否采用金丝雀测试、A/B测试、线上用户行为监控等生产环境测试技术来评估,是与产品的质量目标相关的,后面我们会讲到。
4. 面向技术、评价产品的测试
这个象限中包含了评估产品的非功能属性,常见的包括评估产品性能的压力测试、容量测试等;评估安全性的动态扫描、渗透测试等。
对于微服务而言,评估性能的测试还可以借助于生产环境,包括将线上流量复制到线下的流量回放测试,以及将未完全开发好的功能通过技术性的隐藏直接发布到生产环境,然后在生产环境中进行测试的暗发布(dark launch),最后还包括评估系统韧性的混沌工程。
2.2.2 测试金字塔
微服务中的测试金字塔在martinfowler.com网站上有8年之久,已经被大量引用,这里不再赘述,如图2-8所示。可以看到,其与Mike Cohn版的测试金字塔形状相似,但测试类型更多,而且包含了探索性测试,这个测试金字塔已经不是单纯对自动化测试的指导策略,而是更加完整的测试指导策略。按照这样的逻辑,我们是可以设计出自己的微服务测试金字塔的。
图2-8 微服务中的测试金字塔(来源:martinfowler.com)
1. 自定义微服务测试金字塔
图2-9是笔者基于常见的大型微服务系统中采用的测试类型设计的自定义测试金字塔,额外增加了静态代码分析以及契约测试。静态代码分析不需要代码实现全部完成,即使是对一行代码也可以分析,依赖性最少,而且仅针对增量代码进行扫描时,速度也很快。契约测试同时包含两种类别的测试:一种是单元测试;另一种是API测试。契约测试貌似不好映射到微服务测试金字塔里,但如果我们从测试的目的、集成的范围大小以及投入来看,它应该位于组件测试与端到端测试之间。
图2-9 自定义的微服务测试金字塔
2. 微服务测试金字塔的变形
相信了解微服务测试金字塔的读者已经多少了解一些不同形态的测试金字塔,如图2-10所示的Spotify所指出的蜂巢型,便是被很多人拿来讲的另一种测试策略。
图2-10 Spotify的蜂巢型微服务测试策略(来源:engineering.atspotify.com)
此类测试金字塔被提出的初衷是解决单个服务内部业务变化过于频繁,导致代码实现变化也非常频繁的问题,它认为最下层的测试数量应该尽量少。但需要注意的是,在这个测试金字塔里最下层是对具体实现细节的测试,并不是单元测试。而这一点恰恰符合单元测试的基本原则:正确的单元测试不应该测试类的具体实现,而应该测试类的行为(即public接口)。
Martin Fowler网站上“The practical test pyramid”一文中,有一个典型的示例。
好的单元测试:如果我输入值x和y,结果是否会是z?
坏的单元测试:如果我输入值x和y,该方法是否会先调用类A,然后调用类B,最后返回类A的结果加上类B的结果?
我们不应该关心微服务中的Service类具体调用了哪些其他类,以及测试类具体实现的细节。
策略不应该僵化,要看当前哪种方式成本会更低,永远在稳定的层级上做测试成本最低。后面我们会专门讨论影响测试策略的因素。
3. 微服务中的缺陷过滤器
曾遭遇过重度污染天气的家庭多少都购买过或者考虑购买空气净化器。空气净化器采用的是分层过滤的策略,一方面保证了过滤的效果;另一方面有效控制了过滤的成本。
图2-11展示了一个常见的空气净化流程。预过滤为第一道防线,拦截大颗粒的污染物,其更新成本最低,可以经常更换或者清洗,最后一层是HEPA高精度滤网,它拦截颗粒最小的PM2.5,成本最为昂贵。之所以在HEPA滤网之前加多层源网是为了避免使用高精度的HEPA滤网去拦截大颗粒的污染物,在不牺牲过滤效果(实际上效果会更好)的情况下,延长高精度滤网的寿命,从而降低长期使用成本。
图2-11 空气净化分层过滤器
在微服务测试中,假如我们把缺陷类比为空气中的污染物,将不同层的测试看成滤网。那么我们就可以获得一个如图2-12所示的软件缺陷过滤器。
图2-12 缺陷过滤器
手工探索性测试能够发现对业务价值影响更大的缺陷,属于高价值的测试方法,但是它是昂贵的,因为集成度越高,所采用的测试越偏向“黑盒”性质。如果将整个软件系统都视为黑盒,那么就意味着我们在对大量的代码做非常间接的测试。如同空气净化中的HEPA滤网一样,假如只使用高层次的测试来保障质量,发现缺陷的成本将非常高昂。因此,通过多层次、大量小规模的测试可以让测试的效率更高。
此外,软件测试是难以穷尽的,缺陷分层过滤器仍然会让缺陷逃逸到用户那里,微服务的生产环境测试也比较重要,我们需将逃逸到生产上的缺陷影响尽量降到最低。降低影响范围的方式包括减少遇到缺陷的用户,控制缺陷持续的时间,在缺陷没有造成影响前发现它。
那么我们就可以在图2-12中加上发布控制(金丝雀测试、A/B测试等)、监控(应用性能、基础设施)以及日志形成微服务的缺陷过滤器,如图2-13所示。
图2-13 微服务中的缺陷过滤器
2.2.3 环境管理策略
一般对小团队而言,3类环境基本就可以满足需求:开发环境(DEV)、测试环境(QA/SIT)、生产环境(Prod)。
而在常见的大型微服务团队中,我们经常见到5类环境:开发环境、测试环境、用户验收测试环境(UAT)、预生产环境(Staging)、生产环境。
之所以这么分,是因为在微服务开发中,不会等所有的微服务都开发完成才进行测试,而是会尽可能早地进行验证来发挥微服务的分布式开发优势,因此这几个环境最重要的区别是当前被测试系统的用途及其外部依赖系统的真实程度,具体见表2-1。在微服务中,不同的服务可能由不同的团队开发,当开发Service A时,开发人员在本地开发完成自己负责的功能后,会将Service A单独部署起来,验证功能是否正常。假如正常,开发人员可能会默认自己的功能开发好了。然而,此时正在开发Service A的开发人员可能是多个人,正在并行开发多个功能,可能产生开发人员意想不到的问题,因此我们就需要另一个测试环境让测试人员进行Service A中多个功能的集成测试。当Service A验证无问题后,此时Service B也可能在同步开发。从整个微服务系统来看,如果整体向外提供业务功能,则需要对多个Service再次做集成测试,这就需要另一个部署了多个团队、多个服务的测试环境,对所有的服务进行系统集成测试。假如我们还有支付的服务、订票的服务,那么意味着当前的微服务中除了测试自己的微服务外,有些场景还需要与外部的服务进行集成验证,验证集成后系统是否可以正常工作,同时要让内部用户进行业务验收测试,此时就考虑使用用户验收测试环境。
表2-1 微服务中的环境管理
此外,对非功能特性要求很高的团队,还会有专门的非功能测试环境(如性能环境、安全环境、可靠性测试环境等),但并不是所有的团队都会这么分。有些团队受限于意识、流程或团队资源等原因,仅仅有功能测试环境,并不会单独去申请非功能的测试环境,例如使用用户验收测试环境作为性能测试环境、安全测试环境,或者是将预生产环境作为性能测试或安全测试环境等。
当我们开发单个服务后,需要部署到测试环境进行端到端测试,其中值得探讨的是对测试环境的管理。当前有3种不同的思路来管理这一层级的测试环境:完全共享的测试环境、完全独立的测试环境、动态虚拟测试环境。
(1)完全共享的测试环境
完全共享的测试环境是最为常见的一种形式,所有微服务开发的最新版本都要部署到同一套环境中。如图2-14所示,我们有3个微服务团队分别开发A、B、C三个微服务:当A服务有一个新功能开发出来后,即将A+1版本服务部署到测试环境中;当B有新功能时,B+1版本也会部署到同一套测试环境中;当C有新版本时,也会部署到同一套环境中。
图2-14 完全共享测试环境
这样做的好处有两点:一是可以尽早与其他微服务团队的工作进行集成;二是节省环境资源。
但这并不是没有代价的,首先,如果各个微服务的开发质量都不是很高,那么大家很难有一个稳定的端到端测试环境;其次,当出现问题时,到底是A服务出现了问题,还是B服务出现了问题,比较难以快速定位出来。
(2)完全独立的测试环境
完全独立的测试环境指每个微服务团队都维护自己的一套完整的微服务环境,如图2-15所示。A服务团队在测试环境中只会部署其他服务的稳定版本,B服务团队、C服务团队也一样。它的好处是,解决了其他微服务不稳定导致测试效率下降的问题。但当一个特性横跨多个服务的新版本时,在当前环境就无法验证了。此外,完全独立的测试环境还存在环境数量过多导致的资源利用率低、管理复杂的问题。
图2-15 完全独立的测试环境
(3)动态虚拟测试环境
动态虚拟测试环境指团队成员可以根据需要随时创建出一个微服务测试环境。一般我们会在当前稳定环境的基础上,加入某个或者某几个服务的新版本来动态创建虚拟环境,典型的环境如图2-16所示。这种方案可以提高大型团队的资源利用效率,同时降低维护的复杂度,因为并不是所有团队都需要创建完整的环境。我们可以通过复用公共基础环境,只创建与基础环境不同的服务,然后通过流量控制技术将不同的请求路由到对应版本的服务上,便可以实现该方案。假如你的团队正在使用Istio,那么恭喜你,Istio能帮助你在很大程度上实现定向路由,如果没有,则笔者建议尽早考虑迁移到Kubernetes和Istio上。
图2-16 动态虚拟测试环境
2.2.4 流水线策略
没有流水线支撑的微服务是难以想象的,接下来,我们了解下如何利用流水线策略来加速质量反馈速度。
通常有两级流水线:一级是单个微服务的流水线;另一级是整个微服务系统的流水线。图2-17展示了一种相对复杂的流水线。
虽然图中有两级,但并不意味着这两级流水线是两条流水线,其实它们应该是一条流水线。之所以在测试环境中合并到一起,是因为从这时起所有服务开始向同一套环境部署。
图2-17 微服务下的流水线示例
注:因版面空间所限,测试环境类别采用英文(缩写)表述。
注意
当服务发布时,有两种可能:一是所有的服务开发完成后一起发布;二是每个服务单独发布。
第一种曾被戏称为分布式单体,即每个服务可以独立开发、测试、部署,但无法独立发布。
第二种是更加标准的微服务系统,每个服务可以做到独立发布。
笔者见过的绝大多数团队都采用第一种形式,除了架构没有实现足够的松耦合外,其中另一个重要的因素就是其流水线上的分层测试类型覆盖不足,执行效率不高,无法给予团队足够的信心。