调试间歇性失败规范的系统方法是什么?

What is a systematic approach to debug intermittently failing specs?

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

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

都是ajax请求,要么提交远程表单,要么点击远程link,然后是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 添加到 expect 语句中。我尝试在 expect 语句之前添加 sleep 30 。我仍然断断续续地通过。

有相当多的信息描述了挑剔的 ajax 测试,但我没有找到太多关于如何调试和修复此类问题的信息。

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

更新

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

那么,有解决办法吗?

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

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

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

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

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

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

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

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

如果您确定服务器端(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
# ..

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

在几个 运行 秒的过程中,跟踪哪些测试失败了。他们都在同一个上下文组中吗?

您是否正在混合和匹配 javascript 测试和非 javascript 测试?如果是,您可能会 运行 陷入数据库问题(我已经看到在上下文块中切换数据库清洁器策略引起的问题)。

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

如果 none 缩小了您的搜索范围,请使用允许您重试失败测试的 gem。

我用了respec-retry in the past, but have found it to be unreliable lately. I've switched to rspec-repeat。我通常在开发中将它们关闭(配置为 1 次尝试)和 运行 多次尝试 CI(通常为 3 次)。这样我就可以了解哪些测试在本地不稳定,但不会让这些测试破坏我的构建(除非它们一直失败)。

TL;DR

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

让我也讲讲故事:)。最近,我们还尝试通过类似设置(Poltergeist、JS 测试)间歇性失败的测试来寻找和修复问题。当整个测试套件 运行 比 dividually 时,测试失败的可能性更大,但在大约三分之一的时间内整个套件成功。这只是套件中的几个测试,大约 10 个随机失败,其他测试似乎一直 运行 正常。

首先,我们确保测试不会因 db t运行cation 问题、遗留记录等原因而失败。我们在失败时刻制作了屏幕截图,以验证页面看起来是否正确。

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

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

我们最终使用了一个不太干净的解决方案 - 我们重写了一些水豚助手,用于点击和与表单交互以在内部使用 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 问题的评论之后,我们决定这就是我们要走的路。

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