Generate Rake test tasks dynamically (based on existing test files) in a Rakefile
Pregunta
I'm generating test tasks dynamically based on existing test files in a Rakefile. Consider you have various unit test files named after the pattern test_<name>.rb
. So what I'm doing is creating a task named after the file name inside the 'test' namespace.
With the code below I can then call all the tests with rake test:<name>
require 'rake/testtask'
task :default => 'test:all'
namespace :test do
desc "Run all tests"
Rake::TestTask.new(:all) do |t|
t.test_files = FileList['test_*.rb']
end
FileList['test_*.rb'].each do |task|
name = task.gsub(/test_|\.rb\z/, '')
desc "Run #{name} tests"
Rake::TestTask.new(:"#{name}") do |t|
t.pattern = task
end
end
end
The above code works, it just seems too much code for simple task generation.
And I still haven't figured out a way to print some description text to the console like puts "Running #{name} tests:"
Is there a more elegant way than the above method?
EDIT: What I really expected to get was an alternative to the loop to define the tasks dynamically but I guess the rake lib doesn't provide any helper to that so I'm stuck with the loop.
Solución
Here's another way to solve the problem using rules in Rake.
A rake rule kicks in whenever rake wants to build "X", and it finds a rules that says "to build X, use Y". We will setup a rule that triggers when someone specifies a target in the format "test:XXX", it will attempt to use a file named "test/test_XXX.rb".
require 'rake/testtask'
task :default => 'test:all'
TEST_FILES = FileList['test/test_*.rb']
namespace :test do
desc "Run all tests"
Rake::TestTask.new(:all) do |t|
t.test_files = TEST_FILES
end
rule /^test:/ => lambda { |tn| "test/test_%s.rb" % tn.gsub(/^test:/,'') } do |rule|
ruby rule.source
end
end
Suppose you have a test file named "test/test_my_code.rb". To execute that test file, just type:
rake test:my_code
The rule is triggered whenever there is a target beginning with "test:" that cannot matched by any other task. It then looks for a file given by the lamdba function. The lambda transforms the target name "test:XXX" into a filename "test/test_XXX.rb". If the filename exists, the body of the rule is executed.
The body of the rule just runs the test file as an executable. This is generally enough to run the tests of a single file. If you need to add library paths (e.g. "lib") to the load path for the tests, you can change the rule body to be something like
ruby "-Ilib", rule.source
Another difference between this and the explicit loop solution is that rake will not print out descriptions for rules, so the "rake -T" output won't include the individual tests in its output.
I don't know if this is better than the the original, but it does give you some options.
Otros consejos
My idea:
namespace :test do
FileList['test_*.rb'].each do |rakefile|
name = rakefile.gsub(/test_|\.rb\z/, '')
desc "Run #{name} tests"
task name do
require_relative rakefile
end
#Define default task for :test
task :default => name
end
end
desc "Run all tests"
task :test => 'test:default'
task :default => 'test'
But I'm not sure if it is a good idea to replace Rake::TestTask.new
with require_relative
.
My solution contains another change:
I replace task
test:allwith
test:defaultand define a new task
test`.
So you get the follwoing result with rake -T
:
rake test # Run all tests
rake test:1 # Run 1 tests
rake test:2 # Run 2 tests
If you want to run all tests, you need rake test
, specific tests can be done with rake test:<name>
You may make it also via Rake::TestTask.new
require 'rake/testtask'
namespace :test do
FileList['test_*.rb'].each do |rakefile|
name = rakefile.gsub(/test_|\.rb\z/, '')
Rake::TestTask.new(:"#{name}") do |t|
t.pattern = rakefile
end
#Define default task for :test
task :default => name
end
end
desc "Run all tests"
task :test => 'test:default'
task :default => 'test'
With rake -T
I get:
rake test # Run all tests
rake test:1 # Run tests for 1
rake test:2 # Run tests for 2
The description is generated.
you may add a:
desc 'Alternative description'
task name
Then you get:
rake test # Run all tests
rake test:1 # Run tests for 1 / Alternative description
rake test:2 # Run tests for 2 / Alternative description
If you want to change the text you may add
#replace description
Rake.application[name].comment.replace("Run #{name} tests")
after the end
of Rake::TestTask.new
. That's ugly code, but Rake::TestTask
doesn't allow to change the description (it would be possible, but it wold be a modification of the class).
Hmmm... the one change I made was about all I could think of. Not sure it is worthy of a full answer but I wanted to make sure I did not corrupt anything. You might also try posting it on Stack Exchange's Code Review
require 'rake/testtask'
task :default => 'test:all'
namespace :test do
desc "Run all tests"
Rake::TestTask.new(:all) do |t|
t.test_files = FileList['test_*.rb']
end
FileList['test_*.rb'].each do |task|
name = task.gsub(/test_|\.rb\z/, '')
desc "Run #{name} tests"
Rake::TestTask.new(:"#{name}") do |t|
t.pattern = task
end
end
end