【问题标题】:Testing rate-limited external API calls with VCR and RSpec使用 VCR 和 RSpec 测试速率受限的外部 API 调用
【发布时间】:2012-10-01 14:15:47
【问题描述】:

在我的 Rails 项目中,我使用 VCR 和 RSpec 来测试与外部 REST Web 服务的 HTTP 交互,该服务只允许每秒调用一次

到目前为止,这意味着我最终会运行我的测试套件,直到它由于来自 Web 服务的“超出调用次数”错误而失败。不过在那个阶段,至少有一些磁带被记录下来,所以我只是不断地运行测试套件,直到最终我把它们全部记录下来,并且套件可以只使用磁带运行(我的 default_cassette_options = { record: :new_episodes })。这似乎不是一种最佳的做事方式,特别是如果我发现我以后需要经常重新录制我的磁带,并且我担心不断的呼叫可能会使我进入网络服务的黑名单(没有测试他们有我知道的服务器)。

所以,我最终尝试在调用 Web 服务之前直接在我的 Rspec it 块中调用 sleep(1),然后将这些调用重构到 VCR 配置中:

spec/support/vcr.rb

VCR.configure do |c|
  # ...
  c.after_http_request do |request, response|
    sleep(1)
  end
end

虽然这似乎工作正常,但有没有更好的方法来做到这一点?目前,如果调用没有磁带的外部服务已经是套件中的最终测试,那么套件会不必要地休眠 1 秒。同样,如果测试套件中没有磁带的 2 个 Web 服务调用之间的时间超过一秒,那么就会有另一个不必要的暂停。有没有人为这些条件制定了任何逻辑来测试,或者有没有办法在 VCR 配置中优雅地做到这一点?

【问题讨论】:

  • 考虑到像这样激进的速率限制,我会直接在客户端中构建节流。据推测,如果它炸毁了测试套件,它也可能在生产中炸毁。
  • +1 好点;甚至没有想到这一点。该计划仅是每天在 cron 作业中调用这些 API 一次,但由于在 cron 期间将完成多次调用,因此问题仍然存在。看起来我有一个客户端设计问题要查看。

标签: ruby-on-rails api rspec rspec2 vcr


【解决方案1】:

首先,我建议不要使用:new_episodes 作为您的记录模式。它有它的用途,但默认 (:once) 通常是你想要的。为了准确起见,您希望将磁带记录为单次通过的 HTTP 请求序列。使用:new_episodes,您最终可以得到包含相隔数月记录但现在正在一起播放的 HTTP 交互的磁带,而真正的 HTTP 服务器可能不会以同样的方式响应。

其次,我鼓励您倾听测试所暴露的痛苦,并找到将大部分测试套件与这些 HTTP 请求分离的方法。你能找到一种方法来让测试只关注客户端,而端到端的验收测试会发出请求吗?如果您将 HTTP 内容包装在一个简单的接口中,则应该很容易用测试替身代替所有其他测试,并且更容易控制您的输入。

不过,这是一个长期修复。在短期内,您可以像这样调整您的 VCR 配置:

VCR.configure do |vcr|
  allow_next_request_at = nil
  filters = [:real?, lambda { |r| URI(r.uri).host == 'my-throttled-api.com' }]

  vcr.after_http_request(*filters) do |request, response|
    allow_next_request_at = Time.now + 1
  end

  vcr.before_http_request(*filters) do |request|
    if allow_next_request_at && Time.now < allow_next_request_at
      sleep(allow_next_request_at - Time.now)
    end
  end
end

这使用钩子过滤器 (as documented) 仅在对 API 主机的真实请求上运行钩子。 allow_next_request_at 用于睡眠所需的最短时间。

【讨论】:

  • 非常感谢您的回复。我同意你和@willglyn 的观点,这些问题暴露了我的服务客户端类中的一些设计问题,并且我需要将我的大部分测试套件与这些请求分离,所以我将对此进行研究。与此同时,你的短期修复对我有用,非常感谢。但是,我确实收到了:real? 符号的错误,当我从filters 中删除它时,我得到了一个`未定义的方法uri' for nil:NilClass(在将r 更改为req 之后)。由于这只是一个短期修复,我暂时不参考filters
  • 很高兴它有帮助。我稍微更新了代码......我认为这现在应该对你有用(虽然,我对 :real? 错误感到困惑——这肯定已经过测试并且有效——你得到了什么错误?)
  • 感谢您的编辑。代码现在按预期工作。我最初得到的错误是undefined method 'real?' for nil:NilClass,但现在lambda 块中的参数数量从2 变为1,这似乎解决了问题。再次感谢您的帮助!
【解决方案2】:

另一种方法是使用 APICache 作为 HTTP 库周围的代理,因为它会代表您处理速率限制。

APICache.get("my_albums", period => 1) do
  FlickrRb.get_all_sets
end

当您尝试调用 API 的频率超过您的限制时,这将引发 APICache::CannotFetch

这是APICache Github repo的链接

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-07-01
    • 2017-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多