Test::Builder2 at 10k Feet

schwern on 2010-09-09T08:15:22

Here's a diagram of the "flow" of assert results through Test::Builder version 1.

                     .-------.
                     | foo.t |
                     '-------'
                         |
                         |
     .-------------.     |     .----------------.
     | Test::More  |<--------->| Test::Whatever |
     '-------------'           '----------------'
            |                           |
            |                           |
            |                           |
            |     .---------------.     |
            '---->| Test::Builder |<----'
                  '---------------'
                          |
                          v
                       .-----.
                       | TAP |
                       '-----'
                          |
                          v
                  .---------------.
                  | Test::Harness |
                  '---------------'


You write foo.t using Test::More and Test::Whatever. These both use the same Test::Builder object. It spits out TAP which Test::Harness converts into something human readable.

The big problem there is Test::Builder is monolithic. There's no further breakdown of responsibilities. It only spits out TAP, and only one version of TAP.

Here's what Test::Builder2 looks like:

                                  .-------.
                 .----------------| foo.t |-------------------.
                 |                '-------'                   |
                 |                    |                       |
                 |                    |                       |
                 v                    v                       v
          .------------.     .----------------.     .------------------.
          | Test::More |     | Test::Whatever |     | Test::NotUpdated |
          '------------'     '----------------'     '------------------'
                 |                    |                       |
                 |                    v                       v
                 |           .----------------.       .---------------.
                 '---------->| Test::Builder2 |<------| Test::Builder |
                             '----------------'       '---------------'
                                      |
                                      v
           .--------------.    .-------------.
           | TB2::History |<---| TB2::Result |
           '--------------'    '-------------'
                                      |
                                      |
    .--------------------------.      |       .---------------------.
    | TB2::Formatter::TAP::v13 |<-----'------>| TB2::Formatter::GUI |
    '--------------------------'              '---------------------'
                  |                                      |
                  v                                      |
  .-------------------------------.                      |
  | TB2::Formatter::Streamer::TAP |                      |
  '-------------------------------'                      |
                  |                                      |
                  v                                      |
               .-----.                                   |
               | TAP |                                   |
               '-----'                                   |
                  |                                      |
                  v                                      v
          .---------------.                     .-----------------.
          | Test::Harness |                     | Pretty Pictures |
          '---------------'                     '-----------------'


It starts out the same, foo.t uses a bunch of test modules including Test::More and Test::Whatever using the same Test::Builder2 object, but it also uses Test::NotUpdated which is still using Test::Builder. That's ok because Test::Builder has been rewritten in terms of Test::Builder2 (more on that below).

Test::Builder2, rather than being a monolith, produces a Test::Builder2::Result object for each assert run. This gets stored in a Test::Builder2::History object for possible later use. It also gets handed to a Test::Builder2::Formatter object, the default is Test::Builder2::TAP::v13 which produces TAP version 13. This is fed to a Streamer that prints it to STDOUT and STDERR which is read by Test::Harness and made human readable.

Because Test::Builder2 is not monolithic, you can swap out parts. For example, instead of outputting TAP it could instead hand results to a formatter that produced a simple GUI representation, maybe a green bar, or something that hooks into a larger GUI. Or maybe one that produces JUnit XML.

Here's how Test::Builder and Test::Builder2 Relate.

        .-----.                                         .-----.
        | TB2 |                                         | TB1 |
        '-----'                                         '-----'
           |                                               |
           |                                               |
           |                                               |
           |                                               |
           v                                               v
    .-------------.        .--------------.         .-------------.
    | TB2::Result |------->| TB2::History |<--------| TB2::Result |
    '-------------'        '--------------'         '-------------'
           |                                               |
           |                                               |
           |                                               |
           |              .----------------.               |
           '------------->| TB2::Formatter |<--------------'
                          '----------------'
                                   |
                                   v
                              .--------.
                              | Output |
                              '--------'


Test::Builder and Test::Builder2 coordinate their actions by sharing the same History and Formatter objects. If you call TB1->ok() it produces a Result object which it hands to the History singleton and the Formatter singleton. If you call TB2->ok() it produces a Result object which it hands to the same History and Formatter objects.

This allows most of the Test::Builder code to remain the same while still coordinating with Test::Builder2. It also allows radically different builders to be made without Test::Builder2 dictating how they're to work.

The downside is that roles applied to Test::Builder2 will not effect Test::Builder. Because of this, Test::Builder may become more closely coupled with Test::Builder2 in the future.

Diagrams by App::Asciio.