July 18, 2011, 7:38 p.m.
posted by jazzjack
Automatically Running Unit Tests
Credit: Pat Eyler
You want to make it easy to run your project's unit test suite. You also want the tests to run automatically before you do a new release of your project.
require ' rake/testtask' Rake::TestTask.new('test') do |t| t.pattern = 'test/**/tc_*.rb' t.warning = true end
This Rakefile makes two assumptions:
To execute your test cases, run the command rake test in the project's top-level directory. The tests are loaded by a new Ruby interpreter with warnings enabled. The output is the same as you'd see from Test::Unit's console runner.
If it's easy to trigger the test process, you'll run your tests more often, and you'll detect problems sooner. Rake makes it really convenient to run your tests.
We can make the test command even shorter by defining a default task. Just add the following line to the Rakefile. The position within the file doesn't matter, but to keep things clear, you should put it before other task definitions:
task "default" => ["test"]
Now, whenever we run rake without an argument, it will invoke the test task. If your Rakefile already has a default task, you should be able to just add the test task to its list of prerequisites. Similarly, if you have a task that packages a new release of your software (like the one defined in Recipe 19.4), you can make the test task a prerequisite. If your tests fail, your package won't be built and you won't release a buggy piece of software.
The Rake::TestTask has a special attribute, libs; the entries in this array are added to Ruby's load path. As mentioned above, the default value is ["lib"], making it possible for your tests to require files in your project's lib/ subdirectory. Sometimes this default is not enough. Your Ruby code might not be in the lib/ subdirectory. Or worse, your test code might change the current working directory. Since lib/ is a relative path, the default value of libs would start out as a valid source for library files, and then stop being valid when the test code changed the working directory.
We can solve this problem by specifying the absolute path to the project's lib directory in the Rakefile. Using an absolute path is generally more stable. In this sample Rakefile, we give the load path the absolute path to the lib and test subdirectories. Adding the test directory to the load path is useful if you need to require a library full of test utility methods:
require ' rake/testtask' lib_dir = File.expand_path('lib') test_dir = File.expand_path('test') Rake::TestTask.new("test") do |t| t.libs = [lib_dir, test_dir] t.pattern = "test/**/tc_*.rb" t.warning = true end
As a project grows, it takes longer and longer to run all the test cases. This is bad for the habit we're trying to inculcate, where you run the tests whenever you make a change. To solve this problem, group the test cases into test suites. Depending on the project, you might have a test suite of all test cases concerning file I/O, another suite for the console interface, and so on.
Let's say that when you're working on the DataFile class, you can get away with only running the file I/O test suite. But before releasing a new version of the software, you need to run all the test cases.
To create a Rake test suite, instantiate a Rake::TestTask instance, and set the test_files attribute to something other than the complete list of test files. This sample Rakefile splits up the test files into two suites.
require ' rake/testtask' Rake::TestTask.new('test-file') do |t| t.test_files = ['test/tc_datafile.rb', 'test/tc_datafilewriter.rb', 'test/tc_datafilereader.rb'] t.warning = true end Rake::TestTask.new('test-console') do |t| t.test_files = ['test/tc_console.rb', 'test/tc_prettyprinter.rb'] t.warning = true end
Invoking rake test-file runs the tests related to file I/O, and invoking rake test-console tests the console interface. The only thing missing is a task that runs all tests. You can either use the all-inclusive task from the Rakefile given in the Solution, or you can create a task that has all the test suites as prerequisites:
task 'test' => ['test-file', 'test-console']
When this test task is invoked, Rake runs the test-file suite and then the test-console suite. Each suite is run in its own Ruby interpreter.