NAME
Test2::Tools::xUnit - Perl xUnit framework built on Test2::Workflow
SYNOPSIS
use Test2::Tools::xUnit;
use Test2::V0;
use DBI;
use Local::SystemUnderTest; # $self->{sys} in this example.
# Tests are indicated with the :Test attribute. You can make multiple
# assertions per test if desired. Each method is called on a new instance.
sub run_without_args_should_return_true : Test {
my $self = shift;
ok $self->{sys}->run, 'run should return true';
}
# Skip tests if you don't want them to be run.
sub skip_test : Test Skip(unwanted side effects) {
my $self = shift;
ok !lives { $self->{sys}->self_destruct() }, "self destruct should die";
}
# Todo tests will be run; failures are reported and ignored.
sub todo_test : Test Todo(not yet implemented) {
my $self = shift;
is $self->{sys}->frobnicate(), 42, 'frobnicate should return 42';
}
# BeforeEach/AfterEach are called once per test method, on an instance.
my $dbh;
sub before_each : BeforeEach {
my $self = shift;
$self->{sys} = Local::SystemUnderTest->new( dbh => $dbh );
}
sub after_each : AfterEach {
my $self = shift;
$dbh->do("DELETE FROM log");
}
# BeforeAll/AfterAll are called once, as class methods.
sub before_all : BeforeAll {
$dbh = DBI->connect(...);
}
sub after_all : AfterAll {
$dbh->disconnect;
}
done_testing;
DESCRIPTION
Test2::Tools::xUnit is an implementation of xUnit for Perl that works with Test2. It uses subroutine attributes to indicate which methods should be called at which times.
Integration with Test2
Rather than needing a separate test runner, all Test2::Tools::xUnit tests will be run when done_testing
is seen.
This allows you to choose to write your tests in multiple *.t
files, each with done_testing
at the end.
Alternatively you could adopt a module structure to share compilation time across your tests, with an approach such as this:
# In t/lib/Local/FooBar/Test.pm
package Local::FooBar::Test;
use Test2::Tools::xUnit;
...
1;
# In t/run.t
use File::Find;
use Test2::Tools::Basic;
find({
wanted => sub {require},
no_chdir => 1,
}, './t/lib');
done_testing;
Extending this example to support running individual test modules is left as an exercise for the reader.
Randomization of test order
Test methods within a class are called in a random order, to help avoid tests accidentally depending on side-effects of other test methods.
Note that if you use Test2::Plugin::SRand (e.g. via Test2::V0), then the random order will be dependent on the seed chosen.
Lack of inheritance
Note that your test classes should not inherit from Test2::Tools::xUnit; importing the module is enough to make the subroutine attributes work.
This behaviour differs from Test::Class, Test::Class::Moose and older implementations of xUnit in other languages.
Default object constructor
If your test class has a 'new' method, it will be called to construct instances of the class.
If no such method exists, Test2::Tools::xUnit will bless a hash into the calling package for you. For example, this test passes:
package Foo;
use Test2::Tools::xUnit;
use Test2::V0;
use Scalar::Util qw(reftype blessed);
sub called_with_reference_to_blessed_hash : Test {
my $self = shift;
is blessed($self), 'Foo';
is reftype($self), 'HASH';
}
done_testing;
Per-method test instance lifecycle
Test2::Tools::xUnit creates a new object per test method, to promote isolation of tests. The following tests both pass, regardless of the order in which they are called:
use Test2::Tools::xUnit;
use Test2::V0;
sub new { bless { list => [] }, shift }
sub add_one_to_list : Test {
my $self = shift;
push @{$self->{list}}, "one";
is @{$self->{list}}, 1, "list should have one element";
}
sub check_list_is_empty : Test {
is @{shift->{list}}, 0, "list should be empty";
}
done_testing;
This behaviour is shared with JUnit, but differs from some other xUnit implementations such as NUnit or Test::Class. For more discussion, see https://martinfowler.com/bliki/JunitNewInstance.html
Choice of object framework
Test2::Tools::xUnit will work with your choice of Perl object system, or none. Here is a fictitious example using Moo:
package Calculator::Test;
use Moo;
use Test2::Tools::xUnit;
use Test2::V0;
use Calculator;
has 'calc' => ( is => 'ro', default => sub { Calculator->new } );
sub addition : Test {
my $result = shift->calc->add(2, 2);
is $result, 4, "2 + 2 should equal 4";
}
done_testing;
The default
value of the calc
attribute combines with the lifecycle described above to make a concise alternative to a BeforeEach
setup method.
SUBROUTINE ATTRIBUTES
- Test
-
Test
indicates that a method should be run as a test.Any number of assertions can be made within a test method.
- Skip(<reason>)
-
Skip
will cause a test method not to be run, but it will be reported as "skipped" in the output.If omitted, the reason defaults to the name of the test method.
- Todo(<reason>)
-
Todo
will cause a test method to be run, but any failure will be reported and ignored.If omitted, the reason defaults to the name of the test method.
- BeforeEach
-
BeforeEach
methods will be run before each test method. They are invoked with the test instance as the first argument, so can store fixtures in the object:sub before_each : BeforeEach { my $self = shift; $self->{srv} = Local::Service->new( ua => $mock_ua, ); } sub test_the_service : Test { my $self = shift; my $res = $self->{srv}->run(foo => 'bar'); # ...make assertions about the response... }
- AfterEach
-
AfterEach
methods will be run after each test method. - BeforeAll
-
BeforeAll
methods will be run once before any tests are started; it runs as a class method before any test instances are constructed, so cannot modify any instance attributes. It can, however, modify package variables, and these will be available to all tests:my $dbh; sub before_all : BeforeAll { $dbh = DBI->connect(...); # ...other db setup... }
This is useful for setting up expensive fixtures such as databases, although you must take care to reset their state between each test; probably in an
AfterEach
method.Because of the risk of breaking test isolation,
BeforeAll
should be used sparingly; ifBeforeEach
would work, use that instead. - AfterAll
-
AfterAll
methods will be run after all tests in the class have completed. Similarly toBeforeAll
, this will be invoked as a class method.sub after_all : AfterAll { $dbh->disconnect; }
COMPATIBILITY
Test2::Tools::xUnit might collide with other Perl modules which handle subroutine attributes, if both try to create MODIFY_CODE_ATTRIBUTES in the caller - see attributes.
Currently only Perl 5.12 and higher are supported.
COPYRIGHT AND LICENSE
Copyright © 2018 CV-Library Ltd.
This is free software; you can redistribute it and/or modify it under the same terms as Perl itself.