0%

使用RpcReplay来做服务的Fake实现

测试者的窘境

测试者的窘境
我想让我的测试跑的飞快! 但是不行,我依赖这么多的服务,我可能得fake,mock,stub这些服务才行。但这些Test Double手段有如下问题:

  • 需要一直维护这些Test Double实现
  • 当Test Double的实现与真实实现不一致时,非常容易出Bug

    如果我能有个又快又健壮的Test Double实现就好了。 —测试者
    我们团队认为我们找到了一个折中的方式来解决这个问题. 这需要一个接下来会详细介绍的工。但总体来说,我们做的是将这样的测试分成两种模式。第一个模式是对着真实的服务测试,我们将我们自己的服务启动起来,其他依赖的服务也启动起来,然后执行测试,在执行这一类测试的过程中,我们把所有在测试中产生的与我们依赖的服务产生的RPC交互写进RPC LOG。这种方式使得我们可以跑一个轻量版本的测试用例。相对于启动这么多昂贵的服务,我们要做的是启动一个dumb实现。这个dumb实现所做的事就是从RPC日志中读取,并将之前录制的RPC应答重放。你验证的代码还是可以变化的,并且测试用例中的断言还在正常的保护你的待测系统。

RpcReplay:

将真实实现的入参及出参记录为日志。
录制真实服务的日志

然后,以录制的日志为基础实现一个轻量级版本的服务,对着TA测试。
注意:当你修改了对待测系统的调用参数时,然后待测系统修改了对其他服务的调用参数时,你需要及时更新你的RPC logs。这是唯一的要求。除了会影响请
求参数的修改,其他任何时候,都可以对着RPC Log版本的测试用例做提交测试。这类测试是飞快的,因为你不需要真的启动18/32个服务。
你试图测试的代码还是不断更新的。
当你修改服务的请求时,更新RPC日志。
这些日志会被检入到代码仓库里,并且是可读的。
基于日志的轻量版本服务

这会不会比较危险?

Q: 如果真实服务修改了TA的实现怎么办?在这种情况下我们会不是是在对着一个过时的log做测试?
A: 如果一个服务修改了接口,这个修改会导致线上故障。举个例子我们依赖服务上线了Version2,但现在我们的测试用例还是对着version1的逻辑写的.因为这逻辑是录制来的,我们现在还在(测试中)使用Version1的逻辑。

关键点在于:如果一个服务上线了break了对其他服务承诺的修改,这样的修改会产生线上故障,所以如果该服务在明天要上线version2,这个修改break了你的服务请求路径,你写的关于你的服务的任何测试都没办法阻止对方做类似的上线。他们可以立刻上线,这会导致所有使用version1协议的其他服务出现故障。在这一点上,你必须要依赖其他团队,因为完备的测试自己的API并在上线前提供一个前移窗口,是他们的责任。当他们把服务升级到version2时,有两个可能:

  1. 这次升级导致了线上故障:这不是你的测试用例需要保护的场景,这是他们的测试用例需要保护的场景。
  2. 另一个可能就是下面要讲的良性修改。

良性升级服务的响应

服务修改了实现,但不会修改接口,那么这个修改是良性的。比如说他们修改了数据格式,但对于RPC的返回格式依然保持不变,或是增加了新的元数据(不会影响你的请求).
我们会跑一个后台job不断的更新日志。这个方式会使得你用的日志版Fake一直保持最新。

请求的每次执行结果会不一样吗?

十分不幸,是会不一样的。
SUT每次发出的请求不一样

只要可能就移除非确定性的行为。确定性行为容易理解,重现,debug。

有些信息确实会不一样,比如请求中带了时间戳,id,IP地址等。对于这些非确定性的字段,就需要在录制是过滤掉这些非确定性字段,而在重放时再单独设置。
录制时去掉非确定性信息
重放时增加非确定性信息

额外的好处

一个服务挂了,不会影响你的测试。因为你的提交测试(pre-commit test)不依赖于真实的服务,而是依赖于日志版Fake的。
服务挂了不影响测试

如下两张图对比了没有/有RpcReplay时的测试稳定性数据:
没有/有RpcReplay时的用例稳定性对比
而且执行速度比之前提高了50%!

文章翻译自

GTAC 2015,https://docs.google.com/presentation/d/1IF30CK1z8xzj8xZxJiE7LBQoyzK770yZuBzJR2pfHng/pub?slide=id.gd8dcf688b_0_0