The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

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; if BeforeEach would work, use that instead.

AfterAll

AfterAll methods will be run after all tests in the class have completed. Similarly to BeforeAll, 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.