深度实践微服务测试
上QQ阅读APP看书,第一时间看更新

2.3 影响微服务测试策略制定的因素

前面讲到的是理想中微服务的测试策略,但是现实中会存在各种各样的限制,让我们无法按照理想情况来进行测试。这刚好阐释了策略是一种平衡的艺术。那么接下来一起看一下有哪些因素会影响微服务测试策略的制定。

2.3.1 质量目标

质量目标对测试策略制定的影响非常大,因为它决定了我们对质量风险的承受程度。对风险的承受程度越低,我们需要的测试种类可能就会越多,测试的颗粒度可能越细,以至于在极端场景下,我们需要穷尽所有的测试方法与测试路径。相反,如果承受能力很高,所需要的测试种类就更少,粒度更粗,只要系统能正常工作即可。

图2-18展示了不同领域对质量风险的承受程度,左边承受能力最低,这些领域不希望缺陷逃逸到生产环境被用户发现,而最右边对风险的承受能力最高,可以让用户来发现问题再反馈给我们。

图2-18 不同产品对团队的质量要求不同

所以,在确认是否适用于生产环境的测试时,我们需要考虑自己的产品属于哪个领域,自己的用户对线上缺陷的容忍程度。假如可以容忍,我们可以通过生产环境的测试换取一定的交付响应能力,通过降低缺陷造成的影响来保证软件质量在可接受的范围。假如不可以容忍,那么我们需要将更多的精力放到生产部署之前,严防缺陷逃逸到线上。在这种模式下,甚至于微服务都不建议独立发布,而是需要与其他服务进行集成测试后才可以上线。

2.3.2 被测系统的具体实现与可测试性

如果我们确定了质量的目标,那么系统的可测试性决定了测试的成本。例如被测试系统代码大量使用了全局变量、类之间耦合严重,那么单元测试的成本可能就很高,因为这类测试将与具体的实现严重耦合,非常脆弱,其可测试性非常差。

可测试性是分层测试思想的重要基础。对于面条式代码的系统,前面金字塔中提到的小型测试、中型测试的实施成本很高,甚至难以测试,因此团队容易倾向于采用端到端测试,例如在图2-19中,该系统仅在UI层有可测试性。

图2-19 面条式的系统

观察系统(或模块)是否具有可测试性,需要考虑两个因素。

1)可被控制的输入:被测试对象的输入可以被定义,而且可被外部控制和进行输入。

2)可被观察的输出:被测试对象的输出可以被定义,而且可被外部观察和获取。

我们通过上面两个要素来划分被测试系统(或模块)的边界,为测试提供入口。在面条式的系统内部,各个模块之间调用复杂、混乱,要清晰定义每个模块的输入/输出是很困难的。没有边界就没有入口,没有入口就无法测试。

在微服务中,每个服务都要有明确的API定义,所以理论上微服务系统中API的可测试性是比较好的。但是类的可测试性就需要大家额外关注,以便于可以通过单元测试进行更快的测试。一个可测试性好的代码,内部质量更高,往往其响应变化的能力也更高,长期维护的成本更低。

2.3.3 人员能力

人员能力也是影响测试策略的重要因素。我们常见的情形是,在一些初级的团队中让开发人员写单元测试并不是一件容易的事。由于没有单元测试对微服务的快速交付进行保障,因此团队会倾向于大量用端到端测试和手工测试,造成第一象限中的测试不足。如果测试人员的自动化能力也不强,则整个团队的自动化测试都可能有问题。

因此,测试微服务架构对于团队人员的能力有较高的要求,如果想更好地实施微服务测试,团队所有的成员都需要提升自己的技能。

2.3.4 开发与测试的协作模式

基于康威定律“设计系统的架构受制于产生这些设计的组织的沟通结构”,即系统架构本质上反映的是组织架构,为了更好地实施微服务架构,需要有兼容微服务架构的组织架构。微服务的组织架构应该切分成一个一个的独立全功能团队,它们负责各自微服务的整个生命。因此,微服务架构期望开发、测试人员在同一个团队。

但是在现实中,由于种种原因,开发与测试团队可能是割裂的。Organizational Patterns of Agile Software Development一书直接指出:违反康威定律的项目将遭遇麻烦,当然这样并不是不能做好微服务,而是代价将极为高昂。由于无形的部门墙存在,双方沟通的成本很高:开发人员并不知道测试人员怎么测试,测试人员也不知道开发人员如何测试,到最后分层测试的结果会演变成两个团队大量进行重复性测试。而在时间有限的情况下,团队难以获得好的测试覆盖率,因为测试资源都被重复测试给消耗掉了。

笔者真实地见过这样的团队,为了在有限的时间内完成测试,每次代码修改团队都会反复测试核心场景,在回归测试时,也没有时间有的放矢地进行回归测试集的选择,而是随机挑选。最终的结果是测试团队频繁回归测试,开发团队频繁修改缺陷,却依然造成漏测。

对于那些长期从事关键系统开发,基于过去的经验仍然坚持独立测试团队的企业,笔者的建议是,我们可以考虑保留独立测试团队,让他们在用户验收测试环境介入,进行“传统的系统集成测试”,以保证关键系统高质量上线,但同时抽调一部分测试人员进入开发团队进行真正的微服务测试,以满足康威定律。

2.3.5 产品演进的不同阶段

我们想一想,任何一个软件产品都会经历这样几个阶段:试验阶段、快速增长阶段、成熟阶段、维护阶段、大幅重构或重写阶段。

试验阶段,验证产品可行性是最重要的,我们需要测试的功能可能在下一次迭代中就已经没有了,或者被进行了大幅度的修改。在这个阶段,假如团队编写自动化测试有难度,那么团队很可能会决定不用任何自动化测试,主要靠手工测试保证业务功能。而编写自动化测试非常熟练的开发人员可能会考虑使用BDD或者TDD的方式,但这部分人员整体数量并不多,则团队在这个阶段更不会考虑开发各种测试代码并将其按金字塔型比例分布。

快速增长阶段,已经证明了产品是可行的。此时,团队对产品质量就有了一定的要求,功能性的缺陷要尽可能少一些,同时需要考虑加入性能测试,去验证系统是否能够应对业务的快速增长。此时,一些核心的业务行为大概率已经成形,因此需要考虑引入检查代码行为变化的测试作为回归测试。单元测试、集成测试、组件测试、端到端测试都开始慢慢有了,防护网逐渐建立起来,以保证新业务的代码变化不会破坏已有的代码行为。此时测试种类开始丰富,有能力的团队开始向测试金字塔靠拢来实现测试效果与测试成本的平衡。换句话说,团队在这时会期望有最优的测试策略。

成熟阶段,产品的需求稳定、节奏可控。此时,为了维持用户对产品的好感,我们开始雕琢软件,期望它的质量越来越好,团队开始重视软件的内部质量,会给系统加入更多单元测试、集成测试,来获取更高的测试覆盖率,同时会通过可测试性来验证当前架构的可维护性。有追求的团队,此时会尽可能地实现金字塔式的测试策略。

维护阶段,新需求已经很少,多数是对缺陷的修复。团队会根据缺陷发生的原因,确定到底应该将测试加到哪一层:如果是逻辑的问题,那么可能用单元测试;如果是服务间交互的问题,可能用契约测试或者集成测试甚至端到端测试来修复。有些之前测试写得很少的团队,此时测试分层结构可能就是个倒金字塔型,那么再按测试金字塔来执行已经没有意义了。测试是需要基于风险来考虑的,如果我们清楚这块出现问题的风险非常低,就没有必要投入时间去加测试了。

大幅重构或者重写阶段,往往因当前架构无法支撑业务的发展,此时下层代码要做较大的调整:有些服务要去掉,有些服务要合并,有些服务要被拆分。在这个阶段,测试的核心目标是保证重构业务的正确性。显然越上层的自动化测试对重构的包容性就越大,因为无论里面怎么变,只要对外的接口、GUI不变,测试结果就是正确的。而底层的单元测试就不一定了,这取决于下层代码里哪些部分是稳定的。假如领域模型是稳定的,只是数据库要换,那么针对领域模型的单元测试是不用变动的,而数据访问层的代码可能需要修改,这会导致之前的集成测试被废弃。如果服务需要重新拆分,那么我们可能会优先增加组件级的接口测试,确保新的微服务API表现出的是我们预期的行为。在大部分重构期,真正有效的测试金字塔模型可能是蜂巢型、冰激凌型的,也可能是沙漏型的。