如何在不运行 rake spec 的情况下为 Rails rspec 测试准备测试数据库?
Posted
技术标签:
【中文标题】如何在不运行 rake spec 的情况下为 Rails rspec 测试准备测试数据库?【英文标题】:How do I prepare test database(s) for Rails rspec tests without running rake spec? 【发布时间】:2011-08-20 10:37:30 【问题描述】:经过重大故障排除后,我发现我需要运行一次rake spec
(我可以使用 control-c 中止),然后才能直接运行 rspec(例如,在我们规范的一个子集上)。我们正在运行 Rails 3.0.7 和 RSpec 2.5.0。
显然,rake 正在运行一些重要的数据库设置任务/代码(我们在根级 rails Rakefile 和可能的其他地方有自定义代码)。
如何在不运行 rake spec
的情况下运行 rake 测试数据库设置任务/代码?
除了能够在文件子集上运行 rspec 之外,我还使用 specjour 将我们的规范传播到多个内核(尚未成功将它们传播到 LAN 中),但我看到了相同的直接运行 rspec 的行为:在 specjour 工作之前,我需要在每个测试数据库(假设两个核心)上运行 rake spec
:
rake spec TEST_ENV_NUMBER=1
control-c (after tests start)
rake spec TEST_ENV_NUMBER=2
control-c (after tests start)
specjour
注意:我的 config/database.yml 有这个测试条目(对于并行测试 gem 来说很常见):
test:
adapter: postgresql
encoding: unicode
database: test<%=ENV['TEST_ENV_NUMBER']%>
username: user
password:
parallel_tests 似乎正确设置了它的数据库,但我们的许多规范都失败了。
我还应该提到,运行 specjour prepare
会导致 Postgres 记录它无法找到数据库的错误,但它会创建它们(没有表)。在随后的运行中,不会记录任何错误,也不会创建表。有可能我的整个问题只是prepare
中的一个bug,所以我在github上报告了。
我认为我可以通过在 .specjour/hooks.rb 中设置 Specjour::Configuration.prepare
在每个 specjour 测试数据库上运行任意代码,所以如果有任何 rake 任务或我需要运行的其他代码,它可能在那里工作。
【问题讨论】:
【参考方案1】:我首先删除了我的测试数据库
rake db:drop RAILS_ENV=test
在尝试创建新的测试数据库时,我遇到了一个问题,因为我的用户帐户与拥有数据库的帐户不同,因此我在 PostgreSQL 中创建了数据库。
在命令提示符中键入psql
,然后运行以下命令来创建一个使用您自己以外的帐户的测试数据库。
CREATE DATABASE your_database_name OWNER your_db_owner;
然后在测试环境中运行您的迁移。
rake db:migrate RAILS_ENV=test
【讨论】:
【参考方案2】:似乎在 Rails 4.1+ 中,最好的解决方案是在您的 rails_helper 中添加 ActiveRecord::Migration.maintain_test_schema!
require 'rspec/rails'
之后。
即您不必再担心必须准备数据库了。
https://relishapp.com/rspec/rspec-rails/docs/upgrade#pending-migration-checks
【讨论】:
【参考方案3】:我建议删除您的测试数据库,然后重新创建并迁移:
bundle exec rake db:drop RAILS_ENV=test
bundle exec rake db:create RAILS_ENV=test
bundle exec rake db:schema:load RAILS_ENV=test
在这些步骤之后,您可以运行您的规范:
bundle exec rspec spec
gerry3 注意到:
一个更简单的解决方案是运行
rake db:test:prepare
但是,如果您使用的是 PostgreSQL,这将不起作用,因为 rails 环境已加载,这会打开数据库连接。这会导致prepare
调用失败,因为无法删除数据库。棘手的事情。
【讨论】:
一个更简单的解决方案是运行rake db:test:prepare
。
使用 Postgres 运行 rake db:test:prepare
没有问题。您一定是出于其他原因看到您的问题。
看起来 rake db:test:prepare
在 Rails 4 中已被弃用。
你可以像这样链接 rake 任务:bundle exec rake db:drop db:create db:schema:load RAILS_ENV=test
rake db:test:prepare
仍然适用于 rails 5 (& 使用 PostgreSQL 10.3)【参考方案4】:
提供的解决方案都需要加载 Rails 环境,在大多数情况下,由于开销非常大且速度非常低,这不是所需的行为。 DatabaseCleaner
gem 也很慢,它给你的应用增加了另一个依赖。
由于上述原因,经过数月的懊恼和烦恼,我终于找到了以下解决方案,正是我所需要的。它很好,简单而且快速。在spec_helper.rb
:
config.after :all do
ActiveRecord::Base.subclasses.each(&:delete_all)
end
最好的部分是:它只会清除那些你有效接触过的表格(未接触过的模型不会被加载,因此不会出现在subclasses
,这也是这样做的原因't work before 测试)。此外,它在测试后执行,因此(希望)绿点会立即出现。
唯一的缺点是,如果您在运行测试之前有一个脏数据库,它将不会被清理。但我怀疑这是一个主要问题,因为外部测试通常不会触及测试数据库。
编辑
看到这个答案已经获得了一些人气,我想编辑它以保持完整性:如果你想清除 所有 表,即使是那些没有被触及的,你应该能够做类似的事情下文中的“黑客”。
Hack 1 - 为 subclasses
方法预加载所有模型
在调用subclasses
之前评估一下:
Dir[Rails.root.join("app", "models", "**", "*.rb")].each(&method(:require))
请注意,此方法可能需要一些时间!
Hack 2 - 手动截断表格
ActiveRecord::Base.connection.tables.keep_if |x| x != 'schema_migrations'
将为您提供所有表名,您可以执行以下操作:
case ActiveRecord::Base.configurations[Rails.env]["adapter"]
when /^mysql/, /^postgresql/
ActiveRecord::Base.connection.execute("TRUNCATE #table_name")
when /^sqlite/
ActiveRecord::Base.connection.execute("DELETE FROM #table_name")
ActiveRecord::Base.connection.execute("DELETE FROM sqlite_sequence where name='#table_name'")
end
【讨论】:
整洁!这很有用。【参考方案5】:在 Spring 化的 Rails 4 应用程序中,我的 bin/setup
通常被扩充为包含
puts "\n== Preparing test database =="
system "RAILS_ENV=test bin/rake db:setup"
这与leviathan's answer 非常相似,加上种子测试数据库,如
rake db:setup
# 创建数据库,加载模式,并使用种子数据进行初始化 (使用db:reset
也可以先删除数据库)
正如评论中提到的,如果我们想先删除数据库,rake db:reset
就是这样做的。
我还发现,与 rake db:test:prepare
相比,这提供了更多反馈。
【讨论】:
【参考方案6】:我在工作中设置 CI 系统时遇到了类似的问题,所以我逐渐建立了一个系统来处理这个问题。这可能不是最好的解决方案,但它适用于我的情况,而且我一直在寻找更好的方法来做事。
我有一个需要设置的测试数据库,但还需要加载种子数据以使我们的测试正常工作。
排除 rake 任务故障的基础是使用 --trace 选项运行 rake 以查看幕后发生的情况。当我这样做时,我发现运行 rake spec 做了很多我可以在自定义 rake 任务中复制(或修改)的事情。
这是我们所做的一个示例。
desc "Setup test database - drops, loads schema, migrates and seeds the test db"
task :test_db_setup => [:pre_reqs] do
Rails.env = ENV['RAILS_ENV'] = 'test'
Rake::Task['db:drop'].invoke
Rake::Task['db:create'].invoke
result = capture_stdout Rake::Task['db:schema:load'].invoke
File.open(File.join(ENV['CC_BUILD_ARTIFACTS'] || 'log', 'schema-load.log'), 'w') |f| f.write(result)
Rake::Task['db:seed:load'].invoke
ActiveRecord::Base.establish_connection
Rake::Task['db:migrate'].invoke
end
这只是一个示例,具体到我们的情况,所以你需要弄清楚需要做什么来设置你的测试数据库,但是使用 rake 的 --trace 选项很容易确定.
此外,如果您发现测试设置花费的时间太长(就像在我们的例子中那样),您还可以将数据库转储为 .sql 格式,并将测试数据库通过管道直接导入 mysql 进行加载。通过这种方式,我们可以节省几分钟的测试数据库设置。我没有在这里展示它,因为它使事情变得非常复杂——它需要正确生成而不会过时等等。
HTH
【讨论】:
是的,我已经用 --trace 运行了 rake spec 并尝试在我的 prepare specjour 挂钩中复制它的一些任务,但它还没有工作。我可能会编写一个完全独立的 rake 任务来进行设置,但这是我希望避免的另一个步骤。以上是关于如何在不运行 rake spec 的情况下为 Rails rspec 测试准备测试数据库?的主要内容,如果未能解决你的问题,请参考以下文章
附加到 rake db:seed in rails 并在不复制数据的情况下运行它
如何在不影响 SYSTEM/IE 代理的情况下为 Webbrowser Control 设置代理
如何在不使用 @Composable 注释的情况下为撰写函数创建扩展?