Blackstart

Blackstart is a small, subdued library for automated testing in Ruby. It
doesn't depend on anything beyond the Ruby platform and doesn't modify the
environment outside its conventional namespace. It's tested with a primitive
program, not via an automated-testing library.

The blackstart is a small, subdued bird that eats bugs. A black start is when a
power plant starts up without relying on the electrical grid.

Here's an example of a test program that uses Blackstart:

require "blackstart"

exit Blackstart.run Blackstart.add { |adder|
adder.call do
unless "Hello, World!" == ["He", "", "o, Wor", "d!"].join("l")
raise "join did not work as expected"
end
end

adder.call do
unless "sample" == "simple".gsub(/i/, "a")
raise "gsub did not work as expected"
end
end
}

Blackstart.add adds procs to a collection and returns that collection. In the
example, each proc is designed to run a test and, if it fails, raise an error
to signal this.

Blackstart.run runs a sequence of tests and reports information about any that
fail -- that is, any that raise an error. In the example, failure information
would be written to the standard output stream. It returns false if there were
failures; otherwise, it returns true.

The library provides little beyond this, but it's easy to do relatively
sophisticated things with it. Some examples follow.


- Exiting with the appropriate status

Blackstart.run returns false if there were any failures; otherwise, it returns
true. If you use this as the argument to Kernel#exit, your test program will
exit with a successful status only if there were no failures.


- Defining helpers

You may want all your tests to be able to assert something, generate test data,
or perform some other task by sending a message. You could implement this
statelessly in a singleton object or all instances of Object, for example, but
there's another option that may be more convenient. Blackstart.run calls each
test proc in the context of a new Blackstart::Context instance. You can define
instance methods in that class like this:

class Blackstart::Context
def assert boolean
raise "assertion failed" unless boolean
end

def make_products
@cabbage = { :description => "head of cabbage", :price => 125 }
@orange = { :description => "Cara cara navel orange", :price => 100 }
nil
end
end

All of your tests will be able to use them by sending messages to self. These
methods can see and modify the test's state, which is disposable: it will be
discarded after the test is complete and so will not affect later tests.


- Running code before and after each test

You can run code before and after each test by defining a custom
Blackstart::Context#instance_exec. For example:

class Blackstart::Context
def instance_exec(*)
puts "doing setup"
@variable = "example"
super
ensure
puts "doing teardown"
end
end


- Building a test collection in stages

You can build your collection of test objects in stages and run it at the end.
For example:

require "blackstart"

TEST_OBJECTS = []

Blackstart.add TEST_OBJECTS do |adder|
adder.call do
unless "Hello, World!" == ["He", "", "o, Wor", "d!"].join("l")
raise "join did not work as expected"
end
end
end

Blackstart.add TEST_OBJECTS do |adder|
adder.call do
unless "sample" == "simple".gsub(/i/, "a")
raise "gsub did not work as expected"
end
end
end

exit Blackstart.run TEST_OBJECTS

This makes it straightforward to define your tests in multiple files that you
load in your test program.


- Running tests in random order

Blackstart.run runs a sequence of tests in order, but you can pass it a
randomly-ordered sequence. Array#shuffle may be helpful for this. If you do
this, you may also want to print the random seed at the start of your program
so you can re-run your tests in the same order.


- Reporting detailed test descriptions

After a test fails, Blackstart.run writes a description of the test to the
output stream. It gets this description by converting the test object to a
string. When the test object is an instance of Proc, which is what
Blackstart.add produces when used as expected, this string typically includes
the file path and line number where it was defined: helpful, but it won't be
immediately clear what was being tested.

You can improve this by making your own test objects instead of using
Blackstart.add. Blackstart.run does not strictly need a sequence of Proc
instances; it only needs a sequence of objects that convert to Proc instances
in response to to_proc messages. Your custom test objects can respond to to_s
with detailed descriptions instead of mere file paths and line numbers.


- Handling failures differently

Blackstart.run handles each failure by sending a puts message to the output
stream, one of its parameters. By default, this is $stdout, so the default
behavior is to print unadorned failure information to standard output. But you
can specify any object as the output stream -- even if it's not really a stream
-- allowing you to handle failure information however you want.