微服务下混乱的调用关系
开发团队开始采用微服务架构开发系统的时候,我的测试团队也开始同步学习对应的测试技术,我也像从前一样,逐步封装自己的测试框架,并且采用Postman和Python代码,完成接口测试脚本的快速积累,同时引入了参数类,完成了Excel参数的封装调用。
在开始的一些项目中,只要开发工程师提交了代码仓库主干的合并请求后,除去代码的静态扫描外,持续集成平台会自动调取一个开源的智能化单元测试框架,来完成单元测试,通过后它会自动部署被测系统,然后再执行测试脚本,这整个流程全部是流水线自动驱动完成的。
一般来说,开发工程师在开发前期就已经定义好了微服务接口,测试工程师和开发工程师几乎是同步开始进行各自的开发任务。但是,这种和谐的工作场景很快就被蜘蛛网一样的微服务调用关系给破坏了,几乎所有的项目都会出现相互依赖的关系,比如说服务A依赖服务B,服务B依赖服务C,如下图所示:
这种混乱主要体现在:
- 当持续集成流水线部署服务A的时候,由于对应的开发工程师团队也在做同步改造,导致测试环境的服务B不可用;
- 由于服务B依赖服务C,而服务C还没有开发完成,导致即使服务A和服务B都没问题,但也没有办法完成服务A的接口测试。
其实这种服务A依赖服务B,服务B依赖服务C的依赖方式还算简单,还有更多微服务随着开发越来越复杂,服务之间的调用关系就像蜘蛛网一样错乱,让你摸不清外部依赖到底有几层,以及一个接口到底依赖了几个外部接口。
这就导致了虽然被测系统已经开发完成,测试脚本也准备就绪,但是测试工作就是没办法进行的悲惨结局。面对这种局面,我当时心里确实很不舒服,因为自己做了那么多努力,到头来却被一个不是由自己负责的服务卡住了工作进度,这感觉就像是用尽了全身的力气,却一拳打到了棉花上,自己有再多的劲儿也没处使。
Mock框架的抉择:用什么实现服务B的替身
那作为测试工程师,面对这样的情形,我们该怎么办呢?
我当时想到的就是使用Mock服务。其实Mock服务是一个错误的说法,关于这一点我推荐你看一下Martin Flower的这篇叫做TestDouble的文章,一般我们将TestDouble服务叫做测试替身,但是如今的国内业界里,绝大部分人已经习惯了叫Mock服务,因此在这里我们也还是叫Mock服务。
针对混乱的调用关系,我的思路是:我的被测服务就是服务A,那么我不用管服务B是不是好用,我只要保障服务A能够走完流程,就可以完成接口测试任务了。循着这个思路,我只要用Mock服务伪装成服务B就万事大吉了,我也不用再关心服务B到底调用了多少服务。
但是在选取Mock服务框架时,我又面临着一个抉择,那就是用什么来实现服务B的替身。现在可以实现Mock服务的框架特别多,但绝大部分都要求你有很好的代码基础,每做一个Mock服务其实就是做了一个简单的服务B,不同的是,它不需要实现原有服务B负载的处理逻辑,只要能按服务B的处理逻辑给出对应返回就可以了。
因此,有些团队也会把这样的服务叫做挡板系统,这个名字很形象。也就是说,我给了Mock服务B的请求参数,它只要按照约定好的返回给我参数就可以了,至于一系列其它验证或者微服务调用,都不在Mock服务的设计内,这就像你对着墙打乒乓球一样,墙是你假设的对手,会把你打过去的球挡回来,而你要做的就是接住墙挡回给你的球。
那么,到底应该怎么选择Mock服务框架呢?
首先,你要基于自己和团队的技术栈来做选择,这决定了你完成服务B替身的速度。你要知道,无论服务B的替身做得多么完美,它只是一个Mock,它存在的意义就是帮助你快速完成服务A的接口测试工作,因此,选择一个学习成本低、上手快并且完全适合你自己技术栈的Mock框架,能让你的测试工作事半功倍。
其次,你要让写好的Mock服务容易修改和维护。Mock服务就是一个在测试过程中替代服务B的替身,就和拍电影时的替身演员一样,替身演员可能有好几个,需要在不同地方拍摄不同的电影片段。Mock服务可能只有一个,也有可能有好几个,为了不同的调用或者测试而存在。但是,Mock服务会随着服务B的变化而变化,如果服务B的请求参数和返回参数有变化,那么Mock服务也要能快速完成修改,并且能马上发挥作用。因此,一个非常容易维护的Mock服务框架,才更能马上快速投入使用,快速发挥作用。
如果你的团队技术基础很好,开发能力很强,那么我建议你用对应语言的Mock框架,例如Java语言的Mockito框架和Python语言的mock框架。
如果你的团队技术基础相对比较薄弱,那么我推荐你看看moco,这个框架在开发Mock服务的时候,提供了一种不需要任何编程语言的方式,你可以通过撰写它约束的Json建立服务,并通过命令启动对应的服务,这就可以快速开发和启动运行你需要的Mock服务。
更重要的是,Json格式的数据文件可以独立完成Mock的服务设计,而且Json的学习成本和Python语言相比,就如同小学一年级的数学和高中数学之间的难度差距一样,就更别说和犹如高等数学的Java语言相比较了。如果你想详细的学习moco,可以直接去它在Github上的项目空间,那里有详细的使用说明和示例代码。
我的Mock服务设计经验
在选择好Mock框架后,你就可以酣畅淋漓地完成各个外部依赖服务的解耦工作了,但是关于Mock服务,我还想告诉你一些我的设计经验。
首先,简单是第一要素。无论原服务B处理了多么复杂的业务流程,你在设计服务B的Mock服务时,只要关心服务B可以处理几种类型的参数组合,对应的服务都会返回什么样的参数就可以了。这样你就能快速抓住Mock服务B的设计核心,也能快速完成Mock服务B的开发。
其次,处理速度比完美的Mock服务更重要。一个Mock服务要能按照原服务正确又快速地返回参数,你不需要把大量的时间都浪费在Mock服务的调用上,它只是用来辅助你完成接口测试的一个手段。你需要让它像打在墙上的乒乓球一样,一触到墙面马上就反弹回来;而不能把球打出后,需要去喝个茶或者坐下休息一会,才能收到反弹回来的球。
如果你的Mock服务很耗时,你在只有一个两个服务时,可能影响还不是很明显,但如果你同时有多个Mock服务,或者需要用Mock服务完成性能测试的时候,这就会变成一个很严重的问题,后续会引发强烈的“蝴蝶效应”,使得整个被测接口的响应速度越来越慢。因此你要建立一套快速的Mock服务,尽最大可能不让Mock服务占据系统的调用时间。
最后,你的Mock服务要能轻量化启动,并且容易销毁。你要时刻注意,Mock服务只是一个辅助服务,因此,任何一个团队都不希望启动一个Mock服务需要等待5分钟,或者需要100M的内存。它要能快速启动、容易修改并且方便迁移。既然Mock服务的定位是轻量化的辅助服务,那么它也要容易销毁,以便你在完成测试后,可以快速且便捷地释放它所占据的资源。
总结
微服务现在已经铺天盖地而来,尤其在中台化战略的推动下,业务中台服务的依赖关系会越来越复杂,并且随着团队内微服务数量越来越多,每个测试团队面临的被测系统都会是一团乱麻,很容易找不到头绪。
为了解决由于微服务间相互依赖而导致的混乱的系统调用关系,我建议你尽快掌握一个Mock服务框架,这样可以让你在混乱中理清思路,快速进行接口测试,交付高质量的项目。
最后我要提醒你的是,选择Mock的技术栈与选择测试框架的技术栈还是有些区别的,在选择Mock技术栈时,你重点要考虑的是学习成本,把学习成本降到最低,才是选择Mock框架的首要关注点。而且你不只要关注自己的学习成本,也要关注你所在团队的学习成本,因为现在每个项目都有可能需要Mock服务,这个时候,就要求每一个项目的测试工程师都具备自己独立建设Mock服务的能力,在Mock服务的技术选型上,还是要以团队整体的技术栈为基础,以自己的技术为参考进行选型