2012-10-11 30 views
1

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

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

所以,我结束了直接调用Web服务之前试图把电话在我的Rspec的itsleep(1)制成,然后重构这些调用成录像机配置:

规格/支持/ vcr.rb

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

尽管这似乎做工精细,有没有更好的方式来做到这一点?目前,如果对已经没有盒式磁带的外部服务的呼叫是套件中的最终测试,则该套件不必要地睡1秒钟。同样,如果测试套件中两次不带磁带的Web服务调用之间的时间超过一次,那么还有一次不必要的暂停。有没有人做过任何一种逻辑来测试这些条件,还是有办法在VCR配置中优雅地做到这一点?

+1

鉴于积极的速率限制这样的一个链接,我将建立节流权到客户端。据推测,如果它炸毁测试套件,它也可能在生产中爆炸。 – willglynn

+0

+1好点;甚至没有想到这一点。该计划仅适用于每天一次的cron作业调用这些API,但由于在cron期间会有多个调用完成,所以此问题仍然存在。看起来我有一个客户端设计问题来看待。 –

回答

3

首先,我建议不要使用: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用于睡眠所需的最短时间。

+0

非常感谢您的回复。我同意你和@willglyn的说法,这些问题暴露了我的服务客户端类中的一些设计问题,并且我需要将大部分测试套件与这些请求分离开来,所以我会仔细研究一下。与此同时,你的短期解决方案为我工作,非常感谢。然而,我确实得到了':real?'符号的错误,并且当我从'filters'中删除它时,我得到了一个'未定义的方法'uri'for nil:NilClass'(将'r'改为'req'后)。由于这只是一个短期修复,所以我现在不做引用“过滤器”。 –

+0

很高兴帮助。我更新了一些代码......我认为这应该适用于你(尽管,我对':real?'错误感到困惑 - 这是经过测试的,它的工作原理 - 你得到了什么错误?) –

+0

感谢编辑。代码现在按预期工作。我最初得到的错误是'未定义的方法'是真的吗?'对于nil:NilClass',但现在'lambda'块中的参数数量从2变为1,似乎解决了这个问题。非常感谢您的帮助! –

3

另一种方法可能是使用API​​Cache作为HTTP库的代理,因为它将代表您处理速率限制。

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

当您尝试调用API的次数超过您的限制时,将会提升APICache::CannotFetch

这里给APICache Github repo

+0

不错,谢谢。我会检查出来的。 –