如何针对最终一致的服务编写测试?


17

我正在Google App Engine数据存储区上构建服务,这是最终一致的数据存储区。对于我的应用程序,这很好。

但是,我正在开发测试,这些测试执行的操作类似PUT对象,然后执行GET对象,并检查返回对象的属性。不幸的是,因为数据存储最终是一致的,所以这些简单的测试无法重现。

您如何测试最终一致的服务?


2
您为什么首先要测试期望针对外部服务的可重复性?

...而您实际上要测试什么?您的代码?还是谷歌的?

5
我正在测试整个系统。即,它们是集成测试,而不是单元测试。
Doug Richardson

3
How can I reproducibly test an eventually consistent service? -不能。您必须删除“可复制”或“最终”一词;你不能两者兼得。
罗伯特·哈维

1
如果它最终是一致的,则无论结果是否可重复,任何结果都将是成功的。您已经说过,对您的应用程序来说很好,那么您真正在测试什么?万一?与GAE集成?您的密码?
Laiv

Answers:


16

设计功能测试时,请考虑非功能需求-如果您的服务具有“在x内保持一致(秒/分钟/等)”的非功能需求,只需运行PUT请求,等待x,然后运行GET请求。

届时,如果尚未“到达”数据,则可以认为PUT请求不符合您的要求。


7

您真的希望您的测试快速而一致。如果您开始创建可能由于最终一致性而偶尔失败的测试,则在测试失败时将忽略该测试,那么它有什么用?

创建一个伪造的服务来处理PUT和GET请求,但是还要执行其他操作以使其一致。然后,您的测试是:

datastore.do_put(myobj);
datastore.make_consistent();
validate(datastore.do_get(), myobj);

这使您可以在GET成功检索PUT对象时测试软件的行为。当GET由于服务不一致而找不到对象(或正确的对象)时,它还允许您测试软件的行为。只需忽略对的呼叫即可make_consistent()

与真实服务交互的测试仍然值得,但是它们应该在您正常的开发工作流之外运行,因为它们永远不会100%可靠(例如,如果服务关闭)。这些测试应用于:

  1. 提供在PUT和随后的GET之间保持一致的平均和最坏情况时间的度量;和
  2. 验证您的虚假服务的行为与真实服务相似。参见https://codewithoutrules.com/2016/07/31/verified-fakes/

6

好吧 关键问题是“您正在测试什么”。

  • 我正在测试我的内部逻辑,假设Google工作正常

在这种情况下,您应该嘲笑Google服务,并始终返回响应。

  • 我正在测试我的逻辑可以应付我知道Google会产生的瞬态错误

在这种情况下,您应该模拟Google服务,并始终在正确响应之前返回短暂错误

  • 我正在测试我的产品是否可以与真正的Google服务一起使用

您应该注入真实的Google服务并运行测试。但!您要测试的代码应该内置了Transient Error处理(重试)功能。因此,您应该得到一致的答复。(除非Google行为举止很差)


对于Mock建议+1;如果可以的话,我会为其他选项提供更多的赞成票。
mcottle '16

6

使用以下之一:

  • PUT之后,重试GET N次直到成功。如果N次尝试后没有成功,则失败。
  • 在PUT和GET之间休眠

不幸的是,您必须为这两种技术选择魔术值(N或睡眠持续时间)。


1
您能否澄清:这些替代方案是互补的还是互补的?我认为您的意思是说它们是替代品-这就是我对它们的看法。但是也许我错了。
罗宾·格林

1
正确,我的意思是说它们可以替代。
道格·理查森

2

据我了解,Google Cloud数据存储区允许强一致性查询和最终一致性查询

折衷方案是,高度一致的查询非常严格地限制了速率(在测试过程中您可以接受)。

一种可能是将查询放在包装器中的数据存储中,以实现测试目的的强一致性。

例如,您可以使用称为start_debug_strong_consistency()和的方法end_debug_strong_consistency()

start方法将创建一个可用作所有后续查询的祖先键的键,而end方法将删除该键。

您正在测试的实际查询的唯一更改将是调用setAncestor(your_debug_key)该键(如果存在)。


1

从理论上讲不错,但可能并不总是实用的一种方法是使被测系统中的所有写操作都是幂等的。这意味着,假设您的测试代码按固定的顺序对事物进行测试,则可以重试所有读取所有写入,直到获得期望的结果为止,然后重试直到超出您在测试代码中定义的某个超时。也就是说,执行操作A1,如果有必要,请重试直到结果为B1,然后执行操作A2,如果有必要,请重试直到结果为B2,依此类推。

然后,您无需费心检查写操作的前提条件,因为写操作已经为您检查了它们,您只需重试它们,直到它们成功!

尽可能使用相同的“默认”超时,如果整个系统变慢,则可以增加超时;在重试特别慢的操作时,可以单独覆盖默认值。


1

诸如Google App Engine数据存储区之类的服务基于跨多个全球分布的存在点(POP)的数据复制。任何针对最终一致服务的集成测试实际上都是对该服务在其POP组之间的复制率的测试。取决于许多因素,例如复制方法和各种Internet传输问题,内容在给定服务中传播给每个POP的速率与服务中每个POP的传播速率将不会相同—这是两个示例在任何最终一致的数据存储服务中,报告占大多数报告(至少这是我在主要CDN上工作时的经验)。

为了有效地测试对象在给定平台上的复制,您需要将测试设置为从服务的每个POP中特别请求最近放置的同一对象。我建议测试POP列表1-5次,或者直到POP列表中的所有POP报告包含对象为止。您可以自由调整以下一组执行测试的时间间隔:将其放在数据存储区后的1、5、60分钟,12小时,25小时。关键是记录每个时间间隔的结果,以供以后查看和分析,以了解给定服务的全局复制对象的能力。通常,数据存储区服务仅在本地请求后才将本地副本拉到POP [路由是通过BGP协议完成的,这就是为什么您的测试必须从每个特定POP请求对象才能使其在给定平台上全局有效] 。对于Google的数据存储区,您将要设置测试以从“遍布33个国家/地区的70多个存在点”查询给定对象;您可能必须从Google支持部门获取POP特定的地址网址列表[参考:https://cloud.google.com/about/locations/ ];如果Google使用Fastly进行复制,请快速支持[ https://www.fastly.com/resources ]。

此方法的几个优点:1)您将对给定服务的复制平台有所了解,并在全球范围内整体上了解其优势和弱点(就像在集成测试中一样)。2)对于您测试的任何对象,您都可以使用一种工具来预热内容[发出第一个请求,以给定的本地POP创建副本] –因此,您可以通过一种方法来确保内容在客户请求之前在全球范围内传播地球上任何地方。


0

我有使用Google App Engine数据存储的经验。令人惊讶的是,在本地运行通常比“一致”更“最终”。最简单的示例:创建一个新实体,然后检索它。在过去的5年中,我经常看到本地运行的SDK不会立即找到新实体,而是大约半秒钟后才找到它。

但是,在真正的Google服务器上运行时,我还没有看到这种行为。他们试图使您的数据存储区客户端始终在同一侧与同一服务器上运行,因此通常所有更改都会立即反映在查询中。

我对集成测试的建议是在真实服务器上运行它们,然后您可能无需进行任何虚假的轮询或延迟即可获得结果。


尽管这很方便,但是它可能会导致细微的损坏,涉及到多个应用程序服务器,而这些集成服务器在集成测试中未被发现。我猜他们有充分的理由使本地服务器最终保持一致!
罗宾·格林
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.