重温最快乐的 HTTP 交互:使用 Ruby 的 VCR Gem

发表时间: 2023-01-07 09:27



VCR 是一个 Ruby 库,它记录 HTTP 交互并将它们回放到您的测试套件,验证输入并返回可预测的输出。

在 Ruby 应用程序中,它最常被用作测试工具,但将它放在您的工具箱中可为您提供一组丰富的组织和调试工具,即使您选择不使用其流行的“自动模拟”功能。

如果出现以下情况,这篇文章将对您有所帮助:

  • 您在管理一组复杂的 Webmock 存根时遇到问题。
  • 您需要调试或测试一组复杂或复杂的远程 API 操作。
  • 您正在使用一个使用 VCR 的应用程序。

VCR 和 Webmocks 有什么区别?

重要的是要了解 VCR 和 Webmocks 本质上做同样的事情——主要区别在于外部 API 需要被推理的级别。

使用 Webmocks 时,您为一些常见端点创建“模拟”并为这些端点返回一组已知数据。您需要用您的大脑探索和理解 API 的行为,并自行确定您构建的数据是否有效。这更容易更改,但它要求您不要以与实际 API 不兼容的方式更改 Webmock。

使用 VCR 时,您“记录”了与 API 实时副本的交互。VCR 将这些录音与请求相匹配,并在发出相同请求时“播放它们”。API 本身为您的应用程序提供数据,而 VCR 在可配置的特定级别验证请求。您需要确保您的合作者 API 提供的响应能够为您提供所需的测试用例。这更难更改,也更复杂,但可以保证在录制“磁带”时准确表示 API 的行为。

那么……为什么不直接使用模拟/存根呢?


客户端”模拟有不同的用例

Mock 的主要用例略有不同,即验证通过系统边界的传出消息的属性。它们不应该仅仅用于提供难以安排的数据,除非该数据在不同的测试中根据来源进行了验证。

模拟不是存根。 存根更适合返回虚拟数据,但请注意,存根通常只响应有限的调用集和有限的数据集。无法保证您已经使用存根准确地重现了有问题的 API 的行为。

存根需要深度模拟和对系统的详细了解

Web 服务存根要求您了解系统的内部知识。您必须从底层了解给定区域中的 HTTP 层是如何实现的,然后您需要深入到该区域内部,并重新实现其中的一部分。

尽管这是一个常见问题——而且经常有人反对 VCR,因为当单个 Webmock Stub 请求可以工作时,它会导致大量非常相似的 HTTP 记录——这是我们应用程序中的设计味道。

拆分这些协作者几乎总是一个更好的设计,这样来自外部系统的数据被注入到一个你控制的类中,并且可以很容易地进行单元测试。

例如:

class A  def get_something    results = Net::HTTP.get("https://adomain.com")    parse_results(results)  end  def parse_results    # .. lots of digging, JSON Parsing and conversion logic  endendclass AResult  def self.from_results    # ...lots of digging, JSON Parsing and conversion logic  endendclass AClient  def get_from_api    Net::HTTP.get("https://adomain.com")  endendclass A  def client() = AClient.new  def get_something    client.get_from_api      .then { AResult.from_results(_1)  endend

通过这种设计,您可以围绕AResult包含将一种数据表示形式转换为另一种表示形式所涉及的所有不同逻辑的对象编写更多更快的单元测试。

AClient很容易用“模拟作为外部验证者”来验证。

但是A它自己呢?如果它不仅仅是一个玩具示例,我们很可能也需要对其进行测试。VCR 非常适合此用例,因为它不需要您在服务器上设置一堆不同的状态,而只是验证快乐路径。

你脆弱的眼睛需要休息——你应该写一个测试!

在控制台中徘徊以了解 API 的工作原理既缓慢又容易出错。如果您发现自己在控制台中运行代码,或执行大量 put 语句来发现发生了什么,您应该考虑进行探索性测试。VCR 加速了这个过程,因为它使用 PerfectRobot 知识捕获 API 调用。

随机热点:每个人都一直在做TDD。这只是您是将繁琐的部分自动化还是让计算机来做的问题。您知道如何编写测试并且知道它应该说什么,否则,您如何编写功能?该功能有什么作用?你怎么知道它什么时候起作用?

如果您在搞清楚 API 返回的内容之前一直使用 GraphQL,然后将返回的内容复制到您的 Webmock,那么您正在做与 VCR 完全相同的事情——只是涉及额外的步骤和内容。

录像机不是发射银弹的神锤

有时,手卷存根仍然是正确的方法。VCR 不仅依赖于运行您自己的代码库,还依赖于拥有您正在集成的 API 的现成副本。这可能意味着针对生产端点运行“测试”,或者建立您自己的相关 API 副本。对于是否应该使用 VCR 这个问题的答案对于大多数开发人员来说应该很熟悉—— “嗯,这取决于”。

它要求您拥有正在与之交互的 API 的副本运行,并且您可能需要执行一些设置以利用它进行测试。

对于简单的单次调用 GET 操作,Webmock 可能会更快地处理很多移动部件。

如果 API 更改,VCR 生成的“自动模拟”不会更改。它与 Webmock 共享此限制。

我什么时候应该使用录像机?

在以下情况下,您绝对应该考虑使用 VCR:

  • API 调用顺序或时间是不可预测的。
  • 您对 API 的行为没有很好的理解。
  • 单个逻辑域操作涉及多个 API 调用。
  • API 使用起来很慢、不可靠或令人讨厌。

我什么时候不应该使用 VCR?

VCR 在以下情况下可能有点矫枉过正:

  • 具有格式良好且易于理解的模式的单个 API 调用
  • 您控制的 API 以及您与客户端并行处理的 API
  • 您非常了解的 API,它具有非常明确的行为
  • 在 API 上调用突变会导致难以回滚状态更改,例如删除无法轻松重新创建的记录。

我什么时候应该混合两者?

VCR 最适合大型集成测试。对于与特定 API 状态相关的特定行为,通常最好仅使用 VCR 进行集成测试,然后使用 Webmock 或分解为“难以重现,但功能上可行”的返回值的单元测试。

确信了吗?伟大的。下面是如何使用它。

将它添加到您的 Gemfile,并在您的测试助手中配置它。

Webmock 或类似的是必需的。

VCR.configure do |config|  config.cassette_library_dir = "test/cassettes"  config.hook_into(:webmock)end

当您想在测试中使用它时,请将调用站点包装在 VCR.use_cassette 块中。

VCR.use_cassette("interacting_with_the_api") do  results = ApiClient.do_it!  assert_equal expected, resultsend

请注意,您可以使用“use_cassette”方法在 VCR 中配置被视为“匹配”请求的内容。请小心让 VCR 根据您的测试名称自动命名和录制您的磁带,因为它会在您的测试描述发生变化时重新录制磁带。

尽可能多地使用实时 API。

在您至少确定它会起作用之前,不要记录交互

要让 VCR 在您进行实验时不记录任何内容:

VCR.turn_off!(ignore_cassettes: true)Webmock.enable_net_connect!

VCR 创建 YAML 文件,其中记录了 HTTP 交互。你应该随意删除这些。如有疑问,请将其删除!

不要使用:new_episodes

New Episodes 静默记录无法与现有请求匹配的请求。虽然这是默认选项,但此选项已针对“在集成测试中与许多不同的 API 交互”用例进行了优化。

除非您正在使用该功能,否则请考虑:once— 只在第一次播放时记录,或者:none— 不允许新的 HTTP 请求。

例外情况是将请求拆分到单个 API 时。由于以这种方式组织盒式磁带的主要动机之一是捕获具有不确定行为的 API 交互,因此在单个测试运行的上下文之外记录交互可能很有用。

不过不要让它开着。

用于before_filter记录所有请求。

VCR.configure do |config|  config.before_http_request do |req|    puts req.uri    puts req.body  endend

这是一个非常方便的功能,即使您不使用 VCR 来捕获请求也是如此。它将合理完整地记录有关您的应用程序生成的每个传出请求的信息。req是一个Net:HTTP请求,因此可以获得更多信息,以及 pry 或其他调试器。

用于filter_sensitive_data替换可能因环境而异的敏感数据和值。

config.filter_sensitive_data("<IDENTITY HOST>") { IdentityClient.configs.fetch(:default).host }config.filter_sensitive_data("<BUSINESS PLATFORM HOST>") { Rails.application.config.x.organizations.host }config.filter_sensitive_data("<SHOPIFY KEY>") { Rails.application.config.admin_api_key }

此方法用静态占位符替换盒式磁带中的数据。如果盒式磁带中有“动态数据”,请使用此方法在运行时替换它。

它对于不签入访问 API 可能需要的凭据也非常有用。可以在运行时评估的任何字符串都可以替换为易于识别的占位符。

高级技术:分离出麻烦的合作者

某些 API 可能特别难以表征。某些身份验证 API 可以由其客户端以随机间隔调用随机次数,具体取决于其内部缓存何时过期。为了将这些随机(但至关重要!)的呼叫从盒式磁带中取出用于其他服务,您可以使用前置过滤器将它们隔离到它们自己的盒式磁带中。

这使这些请求保持隔离,但可以隐藏错误,因为测试生成的每个 HTTP 交互不再存在于一个 YAML 文件中。

config.before_http_request(->(r) { URI(r.uri).host == IdentityClient.configs.fetch(:default).host }) do |_req|  VCR.insert_cassette("identity", record: :none, match_requests_on: [:host, :uri, :method, :body])endconfig.after_http_request(->(r) { URI(r.uri).host == IdentityClient.configs.fetch(:default).host }) do |_req|  VCR.eject_cassetteend

高级技术:检查您的 YAML 文件

即使您不打算使用 VCR 生成的自动模拟,记录 HTTP 交互以进行调试仍然很有帮助。您可以将这些与其他交互进行比较,突出显示奇怪的论点或响应,甚至直接修改它们以模拟难以重现的情况。

您甚至可以在 YAML 文件中使用 ERB 来提供动态内容!

现在,无需美国职业棒球大联盟的明确书面许可,就可以开始录制 API!

VCR 是一个功能强大的工具,可将您与 HTTP API 的交互系统化。

该工具适合您的用例吗?一如既往,答案是“视情况而定”。

但是,如果您正在为难以维护的模拟、行为不当的 API 或复杂的多步骤交互而苦苦挣扎,并且想要更可靠、更快且更易于调试的测试,VCR 可以帮助您实现目标。

作者: Stephen Prater

Prater (he/him) is a Staff Engineer at Shopify. He’s been doing Ruby mad-science since 2008 and wants to see the wackiest code you’ve ever written.

出处
:https://shopify.engineering/how-to-program-your-vcr