让我们说我正在测试如何创建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
答案 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