【问题标题】:What is a systematic approach to debug intermittently failing specs?什么是调试间歇性失败规范的系统方法?
【发布时间】:2016-05-09 11:05:31
【问题描述】:

我的 Capybara/Rspec 套件中有四个测试一直失败(CI 部署的真正问题)。

最糟糕的是,这些测试会间歇性地失败,而且通常只有在整个套件运行时才会失败,因此很难调试。

都是ajax请求,要么提交远程表单,要么点击远程链接,后面跟着expect(page).to have_content 'My Flash Message'

这些测试甚至在同一个测试周期内间歇性地失败。例如,我有几个行为相似的模型,所以我正在迭代它们进行测试。

e.g., 
['Country', 'State', 'City'].each do |object|
  let(:target) { create object.to_sym }
  it 'runs my frustrating test' do 
  end
end

有时国家失败,有时国家,有时一切都过去了。

我尝试将wait: 30 添加到期望语句中。我尝试在期望语句之前添加sleep 30。我仍然得到间歇性通行证。

那里有很多描述挑剔的 ajax 测试的信息,但我没有找到太多关于如何调试和修复这类问题的信息。

在我拔掉所有头发之前,我非常感谢其他人的任何建议或指点!

更新

感谢您对所有这些出色的回复。看到其他人也在努力解决类似的问题,这很有用,而且我并不孤单。

那么,有解决办法吗?

使用 pry、byebug、Poltergeist 的调试功能等调试工具的建议(感谢 @Jay-Ar Polidario、@TomWalpole)对于确认我认为我已经知道的事情很有用——即,正如 @BM5K 所建议的那样)这些功能在浏览器中始终如一地工作,并且错误在测试中。

我尝试调整超时和重试(@Jay-Ar Polidario,@BM5K),虽然有所改进,但这些仍然不是一致的修复。更重要的是,这种方法感觉像是修补漏洞,而不是适当的修复,所以我并不完全舒服。

最终,我对这些测试进行了重大改写。这需要分解多步骤功能,并单独设置和测试每个步骤。虽然纯粹主义者可能会声称从用户的角度来看这并不是真正的测试,但每个测试之间有足够的重叠,我对结果感到满意。

在经历这个过程时,我确实注意到所有这些错误都与“点击事物或填写表格”有关,正如@BoraMa 所建议的那样。虽然在这种情况下,体验是相反的——我们采用了 .trigger('click') 语法,因为 capybara + poltergeist 使用 click_linkfind(object).click 报告点击元素时出错,而正是这些测试存在问题。

为了避免这些问题,我尽可能地从测试中删除了 JS。即,在不启用 JS 的情况下测试大部分功能,然后创建非常简短、有针对性的 JS 规范来测试特定的 JS 响应、功能或用户反馈。

所以实际上并没有一个单一的修复方法。老实说,可能需要进行一次重大的重构,并且是一项有价值的练习。通过将所有内容分解为单独的测试,这些测试失去了一些功能,但总的来说,这使得测试更易于阅读和维护。

仍有一些测试偶尔会显示为红色,需要做更多的工作。但总体来说进步很大。

感谢大家的出色指导,并让我放心,测试环境中的交互可能是根本原因。

【问题讨论】:

  • 错误总是一样吗?只是想知道,你能添加一个示例错误吗?我们之前也有间歇性失败的规范,但我们发现这是另一个独立的错误导致它。
  • 始终如一。我期待 Flash 内容 Failure/Error: expect(page).to have_content "#{target.model_name.human} was flagged!" expected to find text "Country was flagged!" in ...... 或选择器 Failure/Error: expect(page).to have_selector "a.unflag-btn" expected to find css "a.unflag-btn" but there were no matches
  • 如果你认为客户端没有问题,下一个要看的地方可能是服务器端(即你没有任何随机变量,例如使用Faker gem ?)。如果此测试是本地连接(它不是远程 AJAX 测试),那么应该没有连接问题,因为它是本地的,因此我只能认为这是服务器端和/或客户端之一/两者导致间歇性错误的一侧。
  • 对于服务器端调试,我通常只是在加载页面的那一行之后插入byebug。那么你可以只检查page 对象及其方法。
  • 显示你实际的“令人沮丧的测试”代码和你得到的实际错误,我们可能会指出它为什么不稳定——你也可以确保你已经打开了 debug: true 和 js_errors:在 poltergeist 驱动程序中为真 - github.com/teampoltergeist/poltergeist#customization - 并发布创建的日志

标签: ruby-on-rails ajax rspec capybara


【解决方案1】:

间歇性失败的测试很难解决,但您可以采取一些措施让生活更轻松。首先是删除任何循环或共享示例。明确说明每个期望应该更清楚地说明哪个示例组合失败了(或者更明显地表明它确实是随机的)。

在多次运行过程中,跟踪哪些测试失败。它们都在同一个上下文组中吗?

您是否混合和匹配 javascript 测试和非 javascript 测试?如果是这样,您可能会遇到数据库问题(我已经看到由于在上下文块中切换数据库清理策略引起的问题)。

确保您考虑测试所在的任何父上下文块。

如果这些都不能缩小您的搜索范围,请使用允许您重试失败测试的 gem。

我过去使用respec-retry,但最近发现它不可靠。我已经切换到rspec-repeat。我通常在开发中将它们关闭(配置为 1 次尝试)并在 CI 上多次尝试(通常为 3 次)。这样我就可以了解哪些测试在本地不稳定,但不会让这些测试破坏我的构建(除非它们始终失败)。

TL;DR

我遇到的大多数间歇性失败的测试都有很多移动部件(rails、capybara、数据库清理器、工厂女孩、phantomjs、rspec 仅举几例)。如果代码经过测试并且规范经常通过并且该功能在浏览器中始终有效,那么您的测试环境中的一些交互是间歇性故障的根本原因。如果您无法追踪到,请重试失败的规范几次。

【讨论】:

  • rspec-repeat 很好,谢谢!现在,当一些随机硒测试无缘无故地决定失败时,我可以享受在半夜没有收到警报的乐趣。
【解决方案2】:

让我也带出故事:)。最近,我们还尝试通过类似设置(Poltergeist、JS 测试)的间歇性失败测试来寻找和解决问题。当整个测试套件运行而不是单独运行时,测试失败的可能性更大,但在大约三分之一的时间内整个套件都成功了。只是套件中的几个测试(大约 10 个)随机失败,其他测试似乎一直运行良好。

首先,我们确保测试没有因为数据库截断问题、剩余记录等而失败。我们在失败的那一刻制作了屏幕截图,以验证页面看起来是否正确。

经过大量搜索后,我们注意到所有剩余的失败测试都涉及点击事物或填写表单,而页面上经常使用 jQuery 动画和其他动态操作。这使我们找到了Poltergeist issue,它最终对我们帮助很大。事实证明,当点击按钮或处理表单输入时,Poltergeist 试图最大限度地模仿普通用户,这可能会在输入/链接动画时导致问题。

认识到这对我们来说确实是一个问题的一种方法是,我们可以成功地find 页面上的元素,但浏览器无法点击它。

我们最终使用了一个不太干净的解决方案 - 我们重写了一些 capybara 助手,用于单击并与表单交互以在内部使用 findtrigger

# override capybara methods as they react badly with animations 
# (click/action is not registered then and test fails)
# see https://github.com/teampoltergeist/poltergeist/issues/530
def click_button(locator, *options)
  find_button(locator, *options).trigger(:click)
end

def click_link(locator, *options)
  find_link(locator, *options).trigger(:click)
end

def choose(locator, *options)
  find(:radio_button, locator, *options).trigger(:click)
end

def check(locator, *options)
  find(:checkbox, locator, *options).trigger(:click)
end

这种方法可能会导致一些意想不到的问题,因为现在您可以点击测试中的内容,即使它们是例如被模态 div 重叠或当它们在页面上不完全可见时。但是在仔细阅读了 github issue 上的 cmets 之后,我们决定这就是我们要走的路。

从那时起,我们只有非常偶然的测试失败,这似乎与另一个 Poltergeist timeouts issue 有关。但是失败是如此罕见,以至于我们没有进一步研究的冲动——测试终于足够可靠了。

【讨论】:

  • 你是对的。我现在记得我们在trigger(:click) 方面也遇到了一些问题。好东西:)
  • 更好的解决方案是在测试模式下禁用动画,而不是冒险触发用户永远无法点击的元素上的事件。
  • 我们实际上已经关闭了所有可能的功能,但既然你这么说,我们可能可以do more(我想我会尝试)。另一方面,它总是要平衡有限的测试环境和真实的用户体验,所以关闭动画也不能保证一切都适合用户。
【解决方案3】:

如果您确定服务器(Rails)和客户端(JS)端都没有变化的变量。如果可行,您可以尝试以下方法。我们用它来解决我们遇到的一些类似问题。

spec/support/wait_for_ajax.rb

# ref: https://robots.thoughtbot.com/automatically-wait-for-ajax-with-capybara
module WaitForAjax
  def wait_for_ajax
    Timeout.timeout(Capybara.default_max_wait_time) do
      loop until finished_all_ajax_requests?
    end
    sleep(1) # ensure just because above doesn't always work
  end

  def finished_all_ajax_requests?
    page.evaluate_script('jQuery.active').zero?
  end
end

spec/features/YOUR_SPEC.rb

Rspec.feature 'My Feature Test', type: :feature do
  ['Country', 'State', 'City'].each do |object|
    let(:target) { create object.to_sym }
    it 'runs my frustrating test' do 
      find('#my-div').click
      wait_for_ajax
    end
  end
end

rails_helper.rb

# ..
RSpec.configure do |config|
  # ..
  config.include WaitForAjax, type: :feature
  # ..
end
# ..

【讨论】:

  • 99% 的时间 wait_for_ajax 只是编写糟糕的测试的一种解决方法,再加上在您的方法中插入 sleep(1) ,这不应该被推荐。
  • 是的,这不是真的推荐。但是,您是否有兴趣进一步阅读我的参考链接。他在那里讨论了竞争条件的这个用例。
  • 阅读您的链接,是的,这种情况很少见,但确实很少见。正如该链接的最后一部分所述,如果您没有以任何视觉方式指示某个操作已完成,那么它很可能是一个糟糕的 UI,如果您是这样,您应该使用 Capybara 等待它而不是使用 wait_for_ajax - wait_for_ajax 真正合理的一个地方是在测试结束后继续的 ajax 请求(因此可能会在清理期间导致数据库问题),并且该问题应该已通过 Capybara 2.7 修复,它等待它们自动完成。
猜你喜欢
  • 1970-01-01
  • 2012-05-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多