如何在功能规范中建立旧期望的结果?

时间:2014-01-02 20:42:52

标签: ruby-on-rails rspec capybara

让我们说我正在测试如何创建Widget:

feature "widget management" do
  scenario "creating a widget_1" do
    visit root_url
    click_link "New Widget"

    fill_in "Name", with: "Awesome Widget"
    click_button "Create Widget"

    expect(page).to have_text("Widget was successfully created.")
  end
end

好的,很好,但是我要说我想创建另一个小部件,并测试这两个小部件如何相互作用。在单元测试中,我可以毫不费力地使用工厂女孩的create方法来设置我需要的哈希值,但是通过功能集成测试,我想要真实地测试整个应用程序,只是为了真的,真的确保没有错误。我不想存根或使用创建方法,我想使用root_url中找到的表单创建两个不同的小部件!

但如果我这样做:

feature "widget management" do
  scenario "creating a widget_1" do
    visit root_url
    click_link "New Widget"

    fill_in "Name", with: "Awesome Widget"
    click_button "Create Widget"

    expect(page).to have_text("Widget was successfully created.")
  end

  scenario "creating a widget_2" do
    visit root_url
    click_link "New Widget"

    fill_in "Name", with: "Awesome Widget_2"
    click_button "Create Widget"

    expect(page).to have_text("Widget_2 was successfully created.")
  end
end

在数据库中创建了令人敬畏的Widget_2,但是最后一个场景中的Awesome_Widget不再位于数据库中。这是因为我的config.use_transactional_fixtures设置为true。我希望测试数据库能够在期望之间进行清理,至少在我的所有单元规范中,因为它们在每次预期之前使用上下文以某种方式设置数据库。

但我不希望在我的广泛集成规范中的每个场景之间清理数据库!我想建立在之前发生的事情上。这是正确的方法吗?或者我应该保留transitional_fixtures并在之前的块中为所有功能规格进行存根/创建?

然后可能创建一个长期望,创建多个小部件并使它们在一个巨大的it块中相互交互?

我只是想模仿真实的行为!我想通过表格,制作成千上万的小部件(使用循环和工厂女孩序列)并且在它上线之前观看它所有工作都是为了安心,(可能使用像Selenium这样的头部服务器)。当然,这是一件明智的事情吗?这样做非常棘手!

我可以理解请求规范中的存根,因为虽然您正在测试使用控制器,模型,视图和活动记录的应用的教师,但您可以单独测试应用的该功能。

虽然有一个功能规格,但你的意思是讲一个故事。用户(或其他)做到这一点,然后他这样做,同时,创建另一个用户,他"朋友"第一个用户,第一个用户"接受"等等。如果数据库在每个期望之间擦拭自己,我真的不知道如何做到这一点!

基本上,如何针对某些规格关闭transactional_fixtures,但是将其用于其他规格,这是否可取?

使用数据库清理程序代替transactional_fixtures!

更新

好吧,这似乎是讲述故事的好方法。使用功能规范: (注意,我只包含与设置相关的代码,你的spec_helper需要更多的东西才能获得rspec,工厂女孩,警卫,无论工作如何)

的Gemfile

(添加database_cleaner gem以便在删除表时更好地控制)

gem 'database_cleaner'

规格/ spec_helper.rb

(配置database_cleaner以删除测试数据库中的所有表,同时将过渡装置设置为true,以便所有表都在期望之间删除(这在功能规范本身中使用实例方法覆盖,您可以使用稍微看一下))

RSpec.configure do |config| 
  config.after(:suite) do
    DatabaseCleaner.clean_with(:truncation)
  end

  config.use_transactional_fixtures = true

  I18n.enforce_available_locales = true   
  config.order = "random"
end

spec / features / integration.rb

最后,功能规格建立在旧的期望基础上,并讲述一个故事'。请注意覆盖有关事务夹具的spec_helper配置的实例方法:

feature "widget management" do
  self.use_transactional_fixtures = false

  scenario "creating a widget_1" do
    visit root_url
    click_link "New Widget"

    fill_in "Name", with: "Awesome Widget"
    click_button "Create Widget"

    expect(page).to have_text("Widget was successfully created.")
  end

  scenario "creating a widget_2" do
    visit root_url
    click_link "New Widget"

    fill_in "Name", with: "Awesome Widget_2"
    click_button "Create Widget" # Both exist in the database and so they can take part in the story!

    expect(page).to have_text("Widget_2 was successfully created.")
  end
end

我想这个问题已经改变了一点!

您对上述内容有何看法?

顺便说一句,如果你正在尝试这个,或者使用数据库清理程序从数据库中手动删除信息的任何东西,请注意如果你使用活动记录,你可能会有点麻烦(你应该在集成规范和模型验证中!基本上,如果上一个规范中的数据由于某种原因在您的数据库中停留(例如,如果您刚刚关闭了transactional_fixtures),那么您的规范可能会失败您已设置的任何唯一性验证,因为相同的数据已经存在在数据库中。

如果您具有上述数据库清理程序设置,则在套件完成时将其设置为清理数据库。因为您的规范遇到了验证错误,所以它永远不会完成,因此数据库清理程序永远不会清理数据库。并且由于数据库尚未清理,当您再次运行规范时,您的规范再次遇到验证错误,它仍然无法完成,数据库清理程序仍然无法清理数据库,因此无限的。

简而言之,如果您收到验证错误,请手动清理表格。

你可以使用sqlite3 shell(比我想的那样比rails控制台稍微容易一点),但如果你愿意,也可以使用它。它与任何shell,postgres,mysql等相似的命令:

在命令行中:

$ cd db
$ sqlite3 test.sqlite3
sqlite > DELETE FROM widgets;

根据您的规范,您可能需要运行该命令几次以清空不同的表。命令的语法:DELETE FROM [要从中删除的表名];

sqlite > .exit

1 个答案:

答案 0 :(得分:2)

  

您对上述内容有何看法,以此来编写功能规范?

我认为在方案之间共享数据不是一个好主意。场景不应该依赖于来自另一场景的数据。如果您在RSpec配置中随机化执行顺序,这将导致问题。

我看到的另一个问题是你并没有真正测试不同的场景。

feature "widget management" do
  self.use_transactional_fixtures = false

  scenario "creating a widget_1" do
    visit root_url
    click_link "New Widget"

    fill_in "Name", with: "Awesome Widget"
    click_button "Create Widget"

    expect(page).to have_text("Widget was successfully created.")
  end

  scenario "creating a widget_2" do
    visit root_url
    click_link "New Widget"

    fill_in "Name", with: "Awesome Widget_2"
    click_button "Create Widget" # Both exist in the database and so they can take part in the story!

    expect(page).to have_text("Widget_2 was successfully created.")
  end
end

这两种情况之间的唯一区别是您改变名称但不是以相关方式。

您可以通过改变输入类型(例如有效然后无效)来改进此套件,以便在代码中测试更多路径。我会重构那个套件看起来更像这样:

describe "widget management" do
  before do
    visit root_url
    click_link "New Widget"

    fill_in "Name", with: name
    click_button "Create Widget"
  end

  context "when creating a widget with a valid name" do
    let(:name) { "Awesome Widget" }

    it "returns a success message" do
      expect(page).to have_text("Widget was successfully created.")
    end
  end

  context "when trying to create a widget with an invalid name" do
    let(:name) { "" }

    it "returns an error message" do
      expect(page).to have_text("Widget_2 must have a valid name.")
    end
  end
end