模拟浏览器 RSpec,没有测试替身泄漏

Mocking a Browser for RSpec, Without Test Doubles Leaking

我发现用 RSpec 模拟的东西完全是有问题的,而且我经常不知道要包含多少代码,因为它是诊断性的。所以我将从我所遇到的情况和我隔离的导致问题的代码开始。

我有需要模拟浏览器的测试。我有一个像这样设置的模拟驱动程序:

require "watir"

def mock_driver
  browser = double("watir")
  allow(browser).to receive(:is_a?).with(Watir::Browser).and_return(true)
  allow(browser).to receive(:driver).and_return(true)
  browser
end

我的测试套件中唯一的问题是这两个测试:

  context "an empiric driver is requested" do
    it "a watir browser is provided" do
      allow(Watir::Browser).to receive(:new).and_return(Empiric.browser)
      Empiric.set_browser mock_driver
    end

    it "the requested watir browser can be shut down" do
      #allow(Empiric.browser).to receive(:quit)
      Empiric.quit_browser
      #allow(mock_browser).to receive(:new).and_return(Empiric.browser)
      #Empiric.set_browser mock_driver
    end
  end

(第二个测试中注释掉的部分是为了说明发生了什么。)

在第二次测试中使用这一行后,我在该测试中收到以下错误:

<Double "watir"> was originally created in one example but has leaked into another
example and can no longer be used. rspec-mocks' doubles are designed to only last for
one example, and you need to create a new one in each example you wish to use it for.

如果我完全注释掉上面的 first 测试,则不会发生该错误,所以我知道我已经隔离了两个相互交互的测试。

好的,现在请注意我的第二个测试的最后一行被注释掉了。 似乎 是错误向我指示的内容。它说我需要在另一个中创建一个新的替身。好的,那么我将更改上次测试:

    it "the requested watir browser can be shut down" do
      #allow(Empiric.browser).to receive(:quit)
      Empiric.quit_browser
      #allow(mock_browser).to receive(:new).and_return(Empiric.browser)
      Empiric.set_browser mock_driver
    end

所以在这里我取消了最后一行的注释,所以我在该测试中建立 mock_driver 并且不允许代码泄漏。

然而,returns 在完全相同的测试中出现完全相同的错误。

我不确定查看该测试中调用的方法是否有帮助,但它们就在这里。首先是 set_browser:

def set_browser(app = :chrome, *args)
  @browser = Watir::Browser.new(app, *args)
  Empiric.browser = @browser
end

这里是 quit_browser:

def quit_browser
  @browser.quit
end

RSpec 认为一个测试 "leaking" 进入另一个测试的事实让我认为也许我的 @browser 实例是问题所在,本质上是两个测试之间持续存在的问题。但我不知道如何解决这个问题。我想也许如果我在第一次测试中退出浏览器,那会有所帮助。所以我将第一个测试更改为:

it "a watir browser is provided" do
  Empiric.quit_browser
  allow(Watir::Browser).to receive(:new).and_return(Empiric.browser)
  Empiric.start_browser mock_driver
end

然而,这导致上述错误现在在两个 测试中显示。

我更准确的猜测是我根本不知道如何在这种情况下提供模拟。

我认为您必须将 allow 与模拟一起使用,而不是 Watir::Browser

例如,如果您允许模拟浏览器接收浏览器将接收的任何调用并让其 return 模拟浏览器,会发生什么情况?

现在您允许 "Watir::Browser" 接收这些消息,这就是 returning 一个 "Empiric.browser"。看看你的代码,我明白你为什么把它放在那里,但我认为这可能是你在这里搞砸的原因。

RSpec 中的模拟是可怕的事情,在这种情况下很少能正常工作。我完全建议不要使用您设置的 mock_driver。相反,对于你的每个测试,只需做一些类似于你在 mock_driver 中所做的事情。我的猜测是您将模拟驱动程序作为共享上下文的一部分包含在内,这也是 非常 RSpec 中的另一件事。不推荐。

相反,您可能希望使用上下文来分解测试。然后对于每个上下文块都有一个 before 块。鉴于您正在模拟浏览器,我不确定您是否应该使用 before:all 或 before:each。但是这样你就可以在之前设置浏览器并在之后将其拆除。

但我建议首先让它在每个测试中单独运行。即使它有很多代码重复。然后,一旦所有测试都通过,重构以将浏览器内容放入那些 before/after 块中。

但是,再次重申,不要使用模拟。不要使用共享上下文。它永远不会结束,老实说,它让你的测试更难推理。

鉴于 Micah 的一些建议,我想提供一个解决方案的答案。我最终这样做了:

  context "an empiric driver is requested" do
    it "a watir browser is provided" do
      allow(Watir::Browser).to receive(:new).and_return(Empiric.browser)
      allow(Empiric.browser).to receive(:driver).and_return(true)
      expect { Empiric.start_browser :some_browser }.not_to raise_error
    end

    it "the requested watir browser can be shut down" do
      allow(Empiric.browser).to receive(:quit)
      allow(Watir::Browser).to receive(:new).and_return(Empiric.browser)
      allow(Empiric.browser).to receive(:driver).and_return(true)
      expect { Empiric.quit_browser }.not_to raise_error
    end
  end

所有这些都是必需的,否则我会遇到一些错误或其他错误。我删除了我的模拟驱动程序,并且根据 Micah 的建议,只是尝试合并似乎有效的内容。以上 "contraption" 是我最终得到的最佳点。

这在覆盖所讨论方法的意义上是有效的。有趣的是,我必须将其添加到我的 RSpec 配置中:

RSpec.configure do |config|
  config.mock_with :rspec do |mocks|
    mocks.allow_message_expectations_on_nil = true
  end
end

我需要这样做,因为 RSpec 报告说我正在调用允许 nil 接收值。

如果你仔细想想,这会带来一些有趣的事情。我有一个明显通过的测试。它增加了我的代码覆盖率。但它 实际上 是在浏览器上测试退出操作吗?好吧,不是真的,因为它正在测试它认为是 nil 的东西的退出操作。

但是 -- 它确实有效。它必须调用有问题的代码行,因为代码覆盖率,正如我的 SimpleCov 所报告的那样,表明已经检查了有问题的语句。