Minitest has a number of strengths: small size, speed, simplicity, and inclusion in the Ruby standard library. Arguably, its small size and simplicity are the greatest, not because they enhance testing, but rather because they encourage modifying Minitest’s behavior.
In the last post (Extending Minitest 5: Assertions),
we saw how easy it is to add new assertions and expectations to our tests. In
this post, we’ll look at extending Minitest by modifying the output of the
ProgressReporter
.
If you’ve done any amount of testing, you’ve seen the results of progress reporters: the string of dots punctuated by the occasional “F”, “S”, or “E” (depending on your test suite). Out of the box, Minitest’s progress reporter is rather dull; it’s your terminal’s foreground color.
We’re going to change that.
In order to get Minitest to automatically read our new progress reporter – and this will be the same for any “plugin” you create – we’ll need to adhere to a few conventions.
In the
::load_plugins
method in the minitest.rb
file of Minitest, it uses
Gem::find_files
to search for plugins. The ::find_files
method uses the
$LOAD_PATH
global variable to determine which directories in which to look.
So, in order for Minitest to find your plugins, you’ll either need to create it
as a Gem, or push
your directory onto the $LOAD_PATH
array.
$LOAD_PATH.push "/home/fooman/projects/rearden"
Furthermore, you will need a “minitest” directory immediately under the
directory you push onto $LOAD_PATH
. Based on our example, our plugin file
would reside under /home/fooman/projects/galt/minitest
.
As the heading implies, all plugins must end with _plugin.rb
. Therefore, if
your plugin is named “whizbang”, it would reside in the file
whizbang_plugin.rb
.
Continuing with the filename example above, you will also need to create initializer methods in your plugin using the following format:
def plugin_whizbang_init
...
end
def plugin_whizbang_options # This one is optional
...
end
The first method (i.e. the “init” method) is required for every Minitest plugin, and we’ll see how that works shortly. The latter method, as mentioned in the code comment, is optional.
What follows is an example plugin which adds Unix escape characters to Minitest’s default output to make the results green (success), yellow (skips), or red (fails and errors). An explanation of the code is below.
module Minitest
def self.plugin_stoplight_init(options)
io = StoplightReporter.new(options[:io])
self.reporter.reporters.grep(Minitest::Reporter).each do |rep|
rep.io = io
end
end
class StoplightReporter
attr_reader :io
def initialize(io)
@io = io
end
def print(o)
case o
when "." then
opening = "\e[32m" # green
when "E", "F" then
opening = "\e[31m" # red
when "S" then
opening = "\e[33m" #yellow
end
io.print opening
io.print o
io.print "\e[m"
end
# Just here so you can see it can be overridden
def puts(*o)
io.puts(*o)
end
def method_missing(msg, *args) # :nodoc:
io.send(msg, *args)
end
end
end
As you can see, we are adding the StoplightReporter
class to the Minitest
module, and we are initializing that class in the plugin_stoplight_init
method.
This method does a couple things:
io
object from the StoplightReporter
classio
object used by every Minitest::Reporter
with that
newly created io
object.We use this new io
object to override the print
method, and as you can see
it’s just wrapping a Unix escape sequence to what is sent. The puts
method is
merely there to show that it can be overridden as well. And the method_missing
catches everything else which might be sent to the original io
object.
Now, anytime Minitest’s reporters output a “.”, “S”, “E”, or “F”, it gets colored.
Of course there’s an easier way to do all of this, but to do it, you have to ignore the plugin system Ryan Davis set up.
module Minitest
class ProgressReporter < Reporter
def record(result)
case result.result_code
when "." then
opening = "\e[32m"
when "E", "F" then
opening = "\e[31m"
when "S" then
opening = "\e[33m"
end
io.print opening
io.print result.result_code
io.print "\e[m"
end
end
end
This is pretty straightforward. All we’re doing here is monkey patching Minitest’s
ProgressReporter
class with our own version, and so avoiding the io
component altogether. To use it, you’ll need to require
it rather than load it
through Minitest’s plugin system.
Of course, there are risks involved with doing things this way, but we’re just playing with the output of a test framework. What could possibly go wrong? :)
Minitest, by design, wants to be extended, and in this post we’ve seen how easy
it is to do just that by modifying the behavior of the ProgressReporter
class.
In the next post, we’ll see what we can do with the results of our test runs.