RSpec + DatabaseCleaner 帮助 -- teardown 过早发生

3
我一直使用基于xUnit的测试框架,对于RSpec有些迷茫,但我正在尝试使用它。然而,规范书写的嵌套结构让我在数据库设置和清理方面感到困惑。
根据DatabaseCleaner的README文件:
Spec::Runner.configure do |config|

  config.before(:suite) do
    DatabaseCleaner.strategy = :transaction
    DatabaseCleaner.clean_with(:truncation)
  end

  config.before(:each) do
    DatabaseCleaner.start
  end

  config.after(:each) do
    DatabaseCleaner.clean
  end

end

现在我不能使用事务,因为我在我的代码中使用了它们,所以我只能坚持截断,但这应该不是重点。
我有这个:
RSpec.configure do |config|
  config.mock_with :rspec

  config.before(:suite) do
    DatabaseCleaner.strategy = :truncation
  end

  config.before(:each) do
    DatabaseCleaner.start
  end

  config.after(:each) do
    DatabaseCleaner.clean
  end
end

这里的问题在于,我在subjectlet块中创建的任何固定装置,在我尝试在后续的describeit块中使用它们时已经消失(从数据库中消失)。

例如(使用Machinist创建固定装置...但这不应该是相关的):

describe User do
  describe "finding with login credentials" do
    let(:user) { User.make(:username => "test", :password => "password") }
    subject { User.get_by_login_credentials!("test", "password") }
    it { should == user }
  end
end

我在如何嵌套describesubject和其他块方面遇到了困难,所以可能这就是我的问题所在,但基本上,当它尝试从数据库获取用户时,由于已经调用了after(:each)挂钩,很可能是在let之后,因此它会失败,因为用户已被删除。


顺便提一下,您仍然可以使用事务来清理数据库。在大多数数据库中,事务可以嵌套(MySQL是一个显著的例外 - 这只是它糟糕的另一种方式)。 - Marnen Laibow-Koser
2个回答

5
如果你想要同时使用subjectlet,你需要了解它们何时被调用。在这种情况下,subject会在由let生成的user方法之前被调用。问题不在于对象在subject被调用之前从数据库中删除,而是在那个时候它甚至还没有被创建。

如果你使用let!方法,你的示例将会工作,它添加了一个before钩子,在示例之前(因此在subject被调用之前)隐式地调用了user方法。

话虽如此,我建议你停止挣扎并使用RSpec已经暴露出来的更简单的API:

describe User do
  it "finds a user with login credentials" do
    user = User.make(:username => "test", :password => "password")
    User.get_by_login_credentials!("test", "password").should eq(user)
  end
end

在我看来,这似乎要简单得多。


谢谢,你说得对,我确实不理解这些块是如何/何时被调用的。最初,我只是将上面代码中的外部describe视为Test::Unit::TestCase子类,将每个it视为xUnit框架中的测试方法。然后我开始阅读别人的规范,他使用了subject ...,然后是一系列的it ... should块,这让我相信我做错了。我想在这之前需要撞墙一会儿才能明白 ;) - d11wtq
如果我需要一个在describe块内的所有测试中共享的fixture,最好的放置位置是哪里?只需使用before(:each)吗?这些其他方法都让我迷失了方向! - d11wtq
很高兴你开始明白了。subject 的原始意图是支持像 it { should validate_presence_of(:some_attr) } 这样的单行匹配器,但它并不真正意味着要出现在规范中。它确实有文档记录,是公开的和可用的,但我越看到它出现在人们的规范中,我就越认为它不应该出现在那里,因为它会带来明显的混淆,尤其是当与 letbefore 钩子一起使用时。 - David Chelimsky
1
通用固定装置的最佳位置是在 before(:each) 钩子中。它简单易懂,意图明确,而且它很有效 :) - David Chelimsky
你应该看一下 let!,它会自动执行给定的代码块作为每个示例的 before 钩子。 - ErJab

3
您写道:
问题在于,我在subject或let块中创建的任何fixture,在我尝试在后续的describe或it块中使用它们时已经消失了(从数据库中消失)。
没错,就是这样。 (而且您并没有像通常的Rails fixtures一样使用fixture,而是使用工厂 - 这很好,因为Rails fixtures很糟糕。)
每个单独的spec(也就是每个it块)都应该从一个原始的数据库开始(或者应该开始)。否则,您的测试会泄露状态并失去原子性。因此,您应该在需要它的规范中创建所需的每个记录(或者,如David所说,在before块中以减少重复)。
关于组织你的规范...按任何有意义的方式进行。通常会有一个外部describe块用于整个类,内部describe块用于相关行为或需要共同设置的规范组。每个describe上下文都可以有自己的beforeafter块。它们像你预期的那样嵌套,因此执行顺序大致如下:
外部before
内部before
规范
内部after
外部after
如果您想查看一个具有大量RSpec规范和Cucumber故事的项目(尽管是旧版本),请查看http://github.com/marnen/quorum2

1
感谢你的解释,非常感激。一旦我开始了,所有的东西都开始变得有意义起来。只是有一些最初的误解。使用 RSpec 真是太棒了!(实际上我大约 14 小时前就开始回复了,我不知道那段时间发生了什么 ;) ... 我想我在输入时切换了标签页)。 - d11wtq
是的,我非常喜欢 RSpec。我发现它的语法鼓励你思考行为,而不是实现细节。 - Marnen Laibow-Koser

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接