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.