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 another 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 [
lambda {
unless "Hello, World!" == ["He", "", "o, Wor", "d!"].join("l")
raise "join did not work as expected"
end
},

lambda {
unless "sample" == "simple".gsub(/i/, "a")
raise "gsub did not work as expected"
end
}
]

Blackstart.run runs a sequence of tests and reports information about any that
fail -- that is, any that raise an error. 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 "before test"
@variable = "example"
super
ensure
puts "after test"
end
end

Use this hack with care because the test code could send instance_exec messages
to self.


- 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 = []

TEST_OBJECTS.concat [
lambda {
unless "Hello, World!" == ["He", "", "o, Wor", "d!"].join("l")
raise "join did not work as expected"
end
}
]

TEST_OBJECTS.concat [
lambda {
unless "sample" == "simple".gsub(/i/, "a")
raise "gsub did not work as expected"
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 before the tests are shuffled
so you can rerun 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, 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 designing your own test objects. 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.