Sign In/My Account | View Cart  
advertisement


Listen Print Discuss

Porting Test::Builder to Perl 6

by chromatic
July 28, 2005

Perl 6 development now proceeds in two directions. The first is from the bottom up, with the creation and evolution of Parrot and underlying code, including the Parrot Grammar Engine. The goal there is to build the structure Perl 6 will need. The second direction is from the top down, with the Pugs project implementing Perl 6 initially separate from Parrot, though recent additions allow an embedded Parrot to run the parsed code and to emit valid Parrot PIR code.

Both projects are important and both help the design of Perl 6 and its implementation. Parrot is valuable in that it demonstrates a solid foundation for Perl 6 (and other similar languages); a far better foundation than the internals of Perl 5 have become. Pugs is important because it allows people to use Perl 6 productively now, with more features every day.

Related Reading

Perl Testing: A Developer's Notebook
By Ian Langworth, chromatic 

Table of Contents
Index
Sample Chapter

Read Online--Safari Search this book on Safari:
 

Code Fragments only

Motivation and Design

Perl culture values testing very highly. Several years ago, at the suggestion of Michael Schwern, I extracted the code that would become Test::Builder from Test::More and unified Test::Simple and Test::More to share that back end. Now dozens of other testing modules, built upon Test::Builder, work together seamlessly.

Pugs culture also values testing. However, there was no corresponding Test::Builder for Perl 6 yet--there was only a single Test.pm module that did most of what the early version of Test::More did in Perl 5.

Schwern and I have discussed updates and refactorings of Test::Builder for the past couple of years. We made some mistakes in the initial design. As Perl 6 offers the chance to clean up Perl 5, so does a port of Test::Builder to Perl 6 offer the chance to clean up some of the design decisions we would make differently now.

Internally, Test::Builder provides a few testing and reporting functions and keeps track of some test information. Most importantly, it contains a plan consisting of the number of tests expected to run. It also holds a list of details of every test it has seen. The testing and reporting functions add information to this list of test details. Finally, the module contains functions to report the test details in the standard TAP format, so that tools such as Test::Harness can interpret the results correctly.

Test::Builder needs to do all of these things, but there are several ways to design the module's internals. Some ways are better than others.

The original Perl 5 version mashed all of this behavior together into one object-oriented module. To allow the use of multiple testing modules without confusing the count or the test details, Test::Builder::new() always returns a singleton. All test modules call the constructor to receive the singleton object and call the test reporting methods to add details of the tests they handle.

This works, but it's a little inelegant. In particular, modules that test test modules have to go to a lot of trouble to work around the design. A more flexible design would make things like Test::Builder::Tester much easier to write.

The biggest change that Schwern and I have discussed is to separate the varying responsibilities into separate modules. The new Test::Builder object in Perl 6 itself contains a Test::Builder::TestPlan object that represents the plan (the number of tests to run), a Test::Builder::Output object that contains the filehandles to which to write TAP and diagnostic output, and an array of tests' results (all Test::Builder::Test instances).

The default constructor, new(), still returns a singleton by default. However, modules that use Test::Builder can create their own objects, which perform the Test::Builder::TestPlan or Test::Builder::Output roles and pass them to the constructor to override the default objects created internally for the singleton. If a test module really needs a separate Test::Builder object, the alternate create() method creates a new object that no other module will share.

This strategy allows the Perl 6 version of Test::Builder::Tester to create its own Test::Builder object that reports tests as normal and then creates the shared singleton with output going to filehandles it can read instead of STDOUT and STDERR. The design appears to be sound; it took less than two hours to go from the idea of T::B::T to a fully working implementation--counting a break to eat ice cream.

First Attempts

Translating Perl 5 OO code into Perl 6 OO code was mostly straightforward, despite my never having written any runnable Perl 6 OO code. (Also, Pugs was not far enough along that objects worked.)

What Went Right

One nice revelation is that opaque objects are actually easier to work with than blessed references. Even better, Perl 6's improved function signatures reduce the necessity to write lots of boring boilerplate code.

Breaking Test::Builder into separate pieces gave the opportunity for several other refactorings. One of my favorite is "Replace Condititional with Polymorphism". There are four different types of tests that have different reporting styles: pass, fail, SKIP, and TODO. It made sense to create separate classes for each of those, giving each the responsibility and knowledge to produce the correct TAP output. Thus I wrote Test::Builder::Test, a façade factory class with a very smart constructor that creates and returns the correct test object based on the given arguments. When Test::Builder receives one of these test objects, it asks it to return the TAP string, passes that message to its contained Test::Builder::TestOutput object, and stores the test object in the list of run tests.

O'Reilly Open Source Convention 2005.

Pages: 1, 2, 3

Next Pagearrow