Let’s say we have a new software project, and we want to have a suite of unit tests for it. It’s easy to put together a nice suite of tests for your project with Rosella. Here’s how.
Basic Harness
First thing, let’s put together a basic test harness. With Rosella’s
tap_harness
library this is extremely easy. Let’s put together a very basic
implementation first:
INIT { pir::load_bytecode("rosella/tap_harness.pbc"); }
my $harness := Rosella::build(Rosella::Harness);
$harness.add_test_dirs("NQP", 't', :recurse(1));
$harness.run();
my $output := Rosella::build(Rosella::Harness::Output::Console);
$output.show_results_summary($harness);
Six lines of code later, we have a harness that automatically reads all tests
from the t/
directory, searching recursively for files. We run the tests
and output a basic summary. Now we can get started with writing some tests,
and later we can make a more interesting harness.
Basic First Tests
Now that we have a basic harness, we can start writing some basic tests. We’re
going to use Rosella’s xunit
library. Here’s our first test file, with some
example tests:
INIT { pir::load_bytecode("rosella/xunit.pbc"); }
Rosella::Testcase::test(MyFirstTest);
class MyFirstTest is Rosella::Testcase {
method test_First() {
Assert::equal(0, 0, "Should be true!");
}
method test_Second() {
Assert::equal(0, 1, "Ooops!");
}
method test_Third() {
self.unimplemented("Haven't written this test yet!");
}
method test_Fourth() {
self.todo("This is probably going to break...");
Assert::equal(1, 2);
}
}
One test passes, one fails, one is listed as unimplemented, and one fails but is marked “todo”. We run this test file manually, and get the following output:
1..4
ok 1 - test_First
not ok 2 - test_Second
# Ooops!
not ok 3 - test_Third # TODO Haven't written this test yet!
# Unimplemented: test_Third
not ok 4 - test_Fourth # TODO This is probably going to break...
# objects are not equal
And, if we run this in our basic harness, we get what we expect:
t/test.t ... not ok (Failed 1 / 4)
Result: FAILED
Passed 3 tests in 1 files
Failed 1 tests in 1 files
We’re well on our way now. Let’s beef up our harness a little bit next.
Better Harness
Recursive searching for tests is nice, but Parrot’s distutils library can hold a list of tests and can feed it into our harness from the commandline. Let’s update the harness to support that.
INIT { pir::load_bytecode("rosella/tap_harness.pbc"); }
MAIN(pir::getinterp()[2]);
sub MAIN(@ARGS) {
pir::shift(@ARGS); # Program name
my $harness := Rosella::build(Rosella::Harness);
$harness.add_test_files("NQP", |@ARGS);
$harness.run();
my $output := Rosella::build(Rosella::Harness::Output::Console);
$output.show_results_summary($harness);
}
Instead of specifying a list of directories to search, now we’re specifying a list of tests. We load them all into the harness, and we run it.
But, maybe that’s not everything we want. We would like to run a quick sanity test first, just to make sure that we can actually load our extension code into Parrot. If we can’t even do that, it doesn’t make any sense to try and run other tests. Plus, if we have a problem with something so fundamental and it would cause all my tests to fail, we want to be able to pinpoint that immediately. Now we are going to update the harness so that it runs a set of sanity tests before running other tests:
INIT { pir::load_bytecode("rosella/tap_harness.pbc"); }
MAIN(pir::getinterp()[2]);
sub MAIN(@ARGS) {
pir::shift(@ARGS);
my $harness := Rosella::build(Rosella::Harness);
$harness.add_test_dirs("NQP", "t/sanity");
$harness.run();
if $harness.run_is_success() {
$harness.add_test_files("NQP", |@ARGS);
$harness.run();
}
my $output := Rosella::build(Rosella::Harness::Output::Console);
$output.show_results_summary($harness);
}
Now if the sanity tests fail we don’t even bother to run any other tests. Anything after the sanity tests would be guaranteed to fail anyway, so it’s no use running them when we know it’s impossible. This saves us time and a lot of error output in our console. Now with that out of the way, let’s revisit our earlier test.
Better Tests
We saw the test output above, the names of the various test methods were
output to the console. That’s nice, but maybe not as descriptive as we could
have. Now we can use the self.verify()
method to give a more understandable
description to the tests:
INIT { pir::load_bytecode("rosella/xunit.pbc"); }
Rosella::Testcase::test(MyFirstTest);
class MyFirstTest is Rosella::Testcase {
method test_First() {
self.verify("Test that 0 == 0. This is basic");
Assert::equal(0, 0, "Should be true!");
}
}
Running this on the console gives us this better-looking output:
1..4
ok 1 - Test that 0 == 0. This is basic
...
Much nicer than what we had before.
Better Harness Output
We would now like to add some improved error-reporting to the harness, so we get a complete listing of troubled-tests. At the end of the harness, we add these two lines:
$output.show_error_detail($harness);
$output.show_failure_detail($harness);
In this context, an “error” is a file that exited prematurely for some reason.
A “failure” is a test failure caused by an Assert::
problem. In both cases,
we won’t output anything if there are no tests in those categories. To show
an example of an “error”, We’re going to add a new test file which fails
parsing. Use your imagination to think of what such a file would look like.
Running the harness now gives us a list of tests that cause us problems:
t/test.t .... not ok (Failed 2 / 5)
t/test2.t ... not ok (aborted prematurely)
# Test aborted with exit code 1
Result: FAILED
Passed 3 tests in 2 files
Failed 1 files due to premature exit
Failed 2 tests in 1 files
List of failed tests by file:
test.t
test 2 - test_Second
test 5 - test_Five
List of files with premature exits:
test2.t
The third and fourth tests from the t/test.t
file were marked as TODO, so
they don’t get included in the output lists. The t/test2.t
file didn’t
execute any tests. That file was a complete failure and gets handled
separately.
So there we have a new test harness and the beginnings of a new unit test suite. All of this, and we haven’t even scratched the surface of what Rosella’s test libraries are capable of doing. In future posts, I’ll talk about some of the more advanced features of these test libraries, and also talk about some of the other cool Rosella libraries.