File: //usr/local/share/perl5/Test/Unit/TestSuite.pm
package Test::Unit::TestSuite;
use strict;
=head1 NAME
Test::Unit::TestSuite - unit testing framework base class
=cut
use base 'Test::Unit::Test';
use Carp;
use Test::Unit::Debug qw(debug);
use Test::Unit::TestCase;
use Test::Unit::Loader;
use Test::Unit::Warning;
=head1 SYNOPSIS
package MySuite;
use base qw(Test::Unit::TestSuite);
sub name { 'My very own test suite' }
sub include_tests { qw(MySuite1 MySuite2 MyTestCase1 ...) }
This is the easiest way of building suites; there are many more. Read on ...
=head1 DESCRIPTION
This class provides the functionality for building test suites in
several different ways.
Any module can be a test suite runnable by the framework if it
provides a C<suite()> method which returns a C<Test::Unit::TestSuite>
object, e.g.
use Test::Unit::TestSuite;
# more code here ...
sub suite {
my $class = shift;
# Create an empty suite.
my $suite = Test::Unit::TestSuite->empty_new("A Test Suite");
# Add some tests to it via $suite->add_test() here
return $suite;
}
This is useful if you want your test suite to be contained in the module
it tests, for example.
Alternatively, you can have "standalone" test suites, which inherit directly
from C<Test::Unit::TestSuite>, e.g.:
package MySuite;
use base qw(Test::Unit::TestSuite);
sub new {
my $class = shift;
my $self = $class->SUPER::empty_new();
# Build your suite here
return $self;
}
sub name { 'My very own test suite' }
or if your C<new()> is going to do nothing more interesting than add
tests from other suites and testcases via C<add_test()>, you can use the
C<include_tests()> method as shorthand:
package MySuite;
use base qw(Test::Unit::TestSuite);
sub name { 'My very own test suite' }
sub include_tests { qw(MySuite1 MySuite2 MyTestCase1 ...) }
This is the easiest way of building suites.
=head1 CONSTRUCTORS
=head2 empty_new ([NAME])
my $suite = Test::Unit::TestSuite->empty_new('my suite name');
Creates a fresh suite with no tests.
=cut
sub empty_new {
my $this = shift;
my $classname = ref $this || $this;
my $name = shift || '';
my $self = {
_Tests => [],
_Name => $name,
};
bless $self, $classname;
debug(ref($self), "::empty_new($name) called\n");
return $self;
}
=head2 new ([ CLASSNAME | TEST ])
If a test suite is provided as the argument, it merely returns that
suite. If a test case is provided, it extracts all test case methods
from the test case (see L<Test::Unit::TestCase/list_tests>) into a new
test suite.
If the class this method is being run in has an C<include_tests> method
which returns an array of class names, it will also automatically add
the tests from those classes into the newly constructed suite object.
=cut
sub new {
my $class = shift;
my $classname = shift || ''; # Avoid a warning
debug("$class\::new($classname) called\n");
my $self = $class->empty_new();
if ($classname) {
Test::Unit::Loader::compile_class($classname);
if (eval { $classname->isa('Test::Unit::TestCase') }) {
$self->{_Name} = "suite extracted from $classname";
my @testcases = Test::Unit::Loader::extract_testcases($classname);
foreach my $testcase (@testcases) {
$self->add_test($testcase);
}
}
elsif (eval { $classname->can('suite') }) {
return $classname->suite();
}
else {
my $error = "Class $classname was not a test case or test suite.\n";
#$self->add_warning($error);
die $error;
}
}
if ($self->can('include_tests')) {
foreach my $test ($self->include_tests()) {
$self->add_test($test);
}
}
return $self;
}
=head1 METHODS
=cut
sub suite {
my $class = shift;
croak "suite() is not an instance method" if ref $class;
$class->new(@_);
}
=head2 name()
Returns the suite's human-readable name.
=cut
sub name {
my $self = shift;
croak "Override name() in subclass to set name\n" if @_;
return $self->{_Name};
}
=head2 names()
Returns an arrayref of the names of all tests in the suite.
=cut
sub names {
my $self = shift;
my @test_list = @{$self->tests};
return [ map {$_->name} @test_list ] if @test_list;
}
=head2 list (SHOW_TESTCASES)
Produces a human-readable indented lists of the suite and the subsuites
it contains. If the first parameter is true, also lists any testcases
contained in the suite and its subsuites.
=cut
sub list {
my $self = shift;
my $show_testcases = shift;
my $first = ($self->name() || 'anonymous Test::Unit::TestSuite');
$first .= " - " . ref($self) unless ref($self) eq __PACKAGE__;
$first .= "\n";
my @lines = ( $first );
foreach my $test (@{ $self->tests() }) {
push @lines, map " $_", @{ $test->list($show_testcases) };
}
return \@lines;
}
=head2 add_test (TEST_CLASSNAME | TEST_OBJECT)
You can add a test object to a suite with this method, by passing
either its classname, or the object itself as the argument.
Of course, there are many ways of getting the object too ...
# Get and add an existing suite.
$suite->add_test('MySuite1');
# This is exactly equivalent:
$suite->add_test(Test::Unit::TestSuite->new('MySuite1'));
# So is this, provided MySuite1 inherits from Test::Unit::TestSuite.
use MySuite1;
$suite->add_test(MySuite1->new());
# Extract yet another suite by way of suite() method and add it to
# $suite.
use MySuite2;
$suite->add_test(MySuite2->suite());
# Extract test case methods from MyModule::TestCase into a
# new suite and add it to $suite.
$suite->add_test(Test::Unit::TestSuite->new('MyModule::TestCase'));
=cut
sub add_test {
my $self = shift;
my ($test) = @_;
debug('+ ', ref($self), "::add_test($test) called\n");
$test = Test::Unit::Loader::load_test($test) unless ref $test;
croak "`$test' could not be interpreted as a Test::Unit::Test object"
unless eval { $test->isa('Test::Unit::Test') };
push @{$self->tests}, $test;
}
sub count_test_cases {
my $self = shift;
my $count;
$count += $_->count_test_cases for @{$self->tests};
return $count;
}
sub run {
my $self = shift;
my ($result, $runner) = @_;
debug("$self\::run($result, ", $runner || 'undef', ") called\n");
$result ||= create_result();
$result->tell_listeners(start_suite => $self);
$self->add_warning("No tests found in " . $self->name())
unless @{ $self->tests() };
for my $t (@{$self->tests()}) {
if ($runner && $self->filter_test($runner, $t)) {
debug(sprintf "+ skipping '%s'\n", $t->name());
next;
}
debug(sprintf "+ didn't skip '%s'\n", $t->name());
last if $result->should_stop();
$t->run($result, $runner);
}
$result->tell_listeners(end_suite => $self);
return $result;
}
sub filter_test {
my $self = shift;
my ($runner, $test) = @_;
debug(sprintf "checking whether to filter '%s'\n", $test->name);
my @filter_tokens = $runner->filter();
foreach my $token (@filter_tokens) {
my $filtered = $test->filter_method($token);
debug(" - by token $token? ", $filtered ? 'yes' : 'no', "\n");
return 1 if $filtered;
}
return 0;
}
sub test_at {
my $self = shift;
my ($index) = @_;
return $self->tests()->[$index];
}
sub test_count {
my $self = shift;
return scalar @{$self->tests()};
}
sub tests {
my $self = shift;
return $self->{_Tests};
}
sub to_string {
my $self = shift;
return $self->name();
}
sub add_warning {
my $self = shift;
$self->add_test(Test::Unit::Warning->new(join '', @_));
}
1;
__END__
=head1 AUTHOR
Copyright (c) 2000-2002, 2005 the PerlUnit Development Team
(see L<Test::Unit> or the F<AUTHORS> file included in this
distribution).
All rights reserved. This program is free software; you can
redistribute it and/or modify it under the same terms as Perl itself.
=head1 SEE ALSO
=over 4
=item *
L<Test::Unit::TestRunner>
=item *
L<Test::Unit::TkTestRunner>
=item *
For further examples, take a look at the framework self test
collection (t::tlib::AllTests).
=back
=cut