运行 一个带参数的 Rake 任务
Running a Rake task with parameters
我一直在努力通过随附的规范获得以下 rake 任务的测试范围。但是,我似乎没有尝试正确发送 env
参数?
测试失败
1) myapp:database tasks myapp:database :recreate works
Failure/Error: system("RAILS_ENV=#{args[:env]} rake db:create")
main received :system with unexpected arguments
expected: (/RAILS_ENV=testing rake db:drop/)
got: ("RAILS_ENV=testing rake db:create")
Diff:
@@ -1,2 +1,2 @@
-[/RAILS_ENV=testing rake db:drop/]
+["RAILS_ENV=testing rake db:create"]
# ./lib/tasks/database.rake:9:in `block (3 levels) in <top (required)>'
# ./spec/lib/tasks/database_rake_spec.rb:17:in `block (5 levels) in <top (required)>'
# ./spec/lib/tasks/database_rake_spec.rb:17:in `block (4 levels) in <top (required)>'
# -e:1:in `<main>'
规格
describe 'myapp:database tasks' do
include_context 'rake'
let(:task_paths) { ['tasks/database', 'tasks/seed'] }
# rubocop:disable RSpec/MultipleExpectations
describe 'myapp:database' do
before do
invoke_task.reenable
end
# TODO!
context ':recreate', focus: true do
let(:task_name) { 'myapp:database:recreate' }
it 'works' do
expect_any_instance_of(Object).to receive(:system).with(/RAILS_ENV=testing rake db:drop/).and_return(true)
expect { invoke_task.invoke('testing') }.to output(
"\nDropping the testing database\n"\
"\nCreating the testing database\n"\
"\nRunning the testing database migrations\n"
).to_stdout
end
end
# rubocop:disable RSpec/MessageSpies
context ':reset' do
let(:task_name) { 'myapp:database:reset' }
it 'works' do
expect(Rake::Task['myapp:database:recreate']).to receive(:invoke).twice
expect(Rake::Task['myapp:seed:all']).to receive(:invoke)
expect { invoke_task.invoke }.to output("\nResetting the development and testing databases\n").to_stdout
end
end
end
# rubocop:enable all
end
任务
namespace :myapp do
namespace :database do
if Rails.env.development? || Rails.env.test?
desc 'Drop and create a database, ["env"] = environment'
task :recreate, [:env] => [:environment] do |_t, args|
puts "\nDropping the #{args[:env]} database\n"
system("RAILS_ENV=#{args[:env]} rake db:drop")
puts "\nCreating the #{args[:env]} database\n"
system("RAILS_ENV=#{args[:env]} rake db:create")
puts "\nRunning the #{args[:env]} database migrations\n"
system("RAILS_ENV=#{args[:env]} rake db:migrate")
end
desc 'Reset the db data and setup development'
task reset: :environment do
puts "\nResetting the development and testing databases\n"
%w(development test).each do |db|
Rake::Task['myapp:database:recreate'].invoke(db)
end
Rake::Task['myapp:seed:all'].invoke
end
end
end
end
共享上下文
shared_context 'rake' do
let(:invoke_task) { Rake.application[task_name] }
let(:highline) { instance_double(HighLine) }
before do
task_paths.each do |task_path|
Rake.application.rake_require(task_path)
end
Rake::Task.define_task(:environment)
end
before do
allow(HighLine).to receive(:new).and_return(highline)
# rubocop:disable all
allow_any_instance_of(Object).to receive(:msg).and_return(true)
allow_any_instance_of(Object).to receive(:error_msg).and_return(true)
# rubocop:enable all
end
end
更新
context ':recreate' do
let(:task_name) { 'myapp:database:recreate' }
it 'works' do
expect_any_instance_of(Object).to receive(:system).with(/RAILS_ENV=testing rake db:drop/).and_return(true)
expect_any_instance_of(Object).to receive(:system).with(/RAILS_ENV=testing rake db:create/).and_return(true)
expect_any_instance_of(Object).to receive(:system).with(/RAILS_ENV=testing rake db:migrate/).and_return(true)
expect { invoke_task.invoke('testing') }.to output(
"\nDropping the testing database\n"\
"\nCreating the testing database\n"\
"\nRunning the testing database migrations\n"
).to_stdout
end
end
正如我在评论中提到的,由于您在此处存根的方式,该任务并未从测试中调用:
expect(Rake::Task['myapp:seed:all']).to receive(:invoke)
虽然这会检查是否调用了 invoke
,但它实际上并没有调用 invoke
(实际上,它使方法 return 为零)。要更改它,您可以:
and_return(<something>)
- 继续
and_call_original
。
在这种情况下,您可能想要使用 and_call_original
,因为您想要调查任务中实际发生的情况。为了在任务中存根单个方法调用,您一直使用的方法 (expect_any_instance_of(Object).to receive(:system)
) 在技术上是可行的,但可能会被重构以与代码更加分离。
例如,您可以将每个 system
调用分离到它自己的方法中(可用于 rake 任务),然后从测试中调用它们。然后为了存根它你只需要传递方法名称。如果需要,您可以单独对这些方法中的每一个进行单元测试,将 system
调用期望放在那里。
我不记得具体在哪里,但我听说它建议不要在 Rake 任务中进行任何实际编程。将您的代码放在常规代码库中的某个位置,并从 rake 任务中调用这些方法。这可以看作是将大型方法重构为较小方法的更通用模式的示例。以这种方式编写代码(也使用函数式风格,但我不会深入探讨)让您在测试时更轻松。
关于你的后续问题:
正如您在测试用例的失败消息中所看到的,实际和预期之间的唯一区别是一个是正则表达式,另一个是字符串。
一个简单的解决方法是更改此行:
expect_any_instance_of(Object).to receive(:system).with(/RAILS_ENV=testing rake db:drop/).and_return(true)
所以 with()
参数是一个字符串,而不是正则表达式
我一直在努力通过随附的规范获得以下 rake 任务的测试范围。但是,我似乎没有尝试正确发送 env
参数?
测试失败
1) myapp:database tasks myapp:database :recreate works
Failure/Error: system("RAILS_ENV=#{args[:env]} rake db:create")
main received :system with unexpected arguments
expected: (/RAILS_ENV=testing rake db:drop/)
got: ("RAILS_ENV=testing rake db:create")
Diff:
@@ -1,2 +1,2 @@
-[/RAILS_ENV=testing rake db:drop/]
+["RAILS_ENV=testing rake db:create"]
# ./lib/tasks/database.rake:9:in `block (3 levels) in <top (required)>'
# ./spec/lib/tasks/database_rake_spec.rb:17:in `block (5 levels) in <top (required)>'
# ./spec/lib/tasks/database_rake_spec.rb:17:in `block (4 levels) in <top (required)>'
# -e:1:in `<main>'
规格
describe 'myapp:database tasks' do
include_context 'rake'
let(:task_paths) { ['tasks/database', 'tasks/seed'] }
# rubocop:disable RSpec/MultipleExpectations
describe 'myapp:database' do
before do
invoke_task.reenable
end
# TODO!
context ':recreate', focus: true do
let(:task_name) { 'myapp:database:recreate' }
it 'works' do
expect_any_instance_of(Object).to receive(:system).with(/RAILS_ENV=testing rake db:drop/).and_return(true)
expect { invoke_task.invoke('testing') }.to output(
"\nDropping the testing database\n"\
"\nCreating the testing database\n"\
"\nRunning the testing database migrations\n"
).to_stdout
end
end
# rubocop:disable RSpec/MessageSpies
context ':reset' do
let(:task_name) { 'myapp:database:reset' }
it 'works' do
expect(Rake::Task['myapp:database:recreate']).to receive(:invoke).twice
expect(Rake::Task['myapp:seed:all']).to receive(:invoke)
expect { invoke_task.invoke }.to output("\nResetting the development and testing databases\n").to_stdout
end
end
end
# rubocop:enable all
end
任务
namespace :myapp do
namespace :database do
if Rails.env.development? || Rails.env.test?
desc 'Drop and create a database, ["env"] = environment'
task :recreate, [:env] => [:environment] do |_t, args|
puts "\nDropping the #{args[:env]} database\n"
system("RAILS_ENV=#{args[:env]} rake db:drop")
puts "\nCreating the #{args[:env]} database\n"
system("RAILS_ENV=#{args[:env]} rake db:create")
puts "\nRunning the #{args[:env]} database migrations\n"
system("RAILS_ENV=#{args[:env]} rake db:migrate")
end
desc 'Reset the db data and setup development'
task reset: :environment do
puts "\nResetting the development and testing databases\n"
%w(development test).each do |db|
Rake::Task['myapp:database:recreate'].invoke(db)
end
Rake::Task['myapp:seed:all'].invoke
end
end
end
end
共享上下文
shared_context 'rake' do
let(:invoke_task) { Rake.application[task_name] }
let(:highline) { instance_double(HighLine) }
before do
task_paths.each do |task_path|
Rake.application.rake_require(task_path)
end
Rake::Task.define_task(:environment)
end
before do
allow(HighLine).to receive(:new).and_return(highline)
# rubocop:disable all
allow_any_instance_of(Object).to receive(:msg).and_return(true)
allow_any_instance_of(Object).to receive(:error_msg).and_return(true)
# rubocop:enable all
end
end
更新
context ':recreate' do
let(:task_name) { 'myapp:database:recreate' }
it 'works' do
expect_any_instance_of(Object).to receive(:system).with(/RAILS_ENV=testing rake db:drop/).and_return(true)
expect_any_instance_of(Object).to receive(:system).with(/RAILS_ENV=testing rake db:create/).and_return(true)
expect_any_instance_of(Object).to receive(:system).with(/RAILS_ENV=testing rake db:migrate/).and_return(true)
expect { invoke_task.invoke('testing') }.to output(
"\nDropping the testing database\n"\
"\nCreating the testing database\n"\
"\nRunning the testing database migrations\n"
).to_stdout
end
end
正如我在评论中提到的,由于您在此处存根的方式,该任务并未从测试中调用:
expect(Rake::Task['myapp:seed:all']).to receive(:invoke)
虽然这会检查是否调用了 invoke
,但它实际上并没有调用 invoke
(实际上,它使方法 return 为零)。要更改它,您可以:
and_return(<something>)
- 继续
and_call_original
。
在这种情况下,您可能想要使用 and_call_original
,因为您想要调查任务中实际发生的情况。为了在任务中存根单个方法调用,您一直使用的方法 (expect_any_instance_of(Object).to receive(:system)
) 在技术上是可行的,但可能会被重构以与代码更加分离。
例如,您可以将每个 system
调用分离到它自己的方法中(可用于 rake 任务),然后从测试中调用它们。然后为了存根它你只需要传递方法名称。如果需要,您可以单独对这些方法中的每一个进行单元测试,将 system
调用期望放在那里。
我不记得具体在哪里,但我听说它建议不要在 Rake 任务中进行任何实际编程。将您的代码放在常规代码库中的某个位置,并从 rake 任务中调用这些方法。这可以看作是将大型方法重构为较小方法的更通用模式的示例。以这种方式编写代码(也使用函数式风格,但我不会深入探讨)让您在测试时更轻松。
关于你的后续问题:
正如您在测试用例的失败消息中所看到的,实际和预期之间的唯一区别是一个是正则表达式,另一个是字符串。
一个简单的解决方法是更改此行:
expect_any_instance_of(Object).to receive(:system).with(/RAILS_ENV=testing rake db:drop/).and_return(true)
所以 with()
参数是一个字符串,而不是正则表达式