Test::Roo(3pm)

πŸ“¦
version 1.004 Β· 4 versions
, in debian bookworm, debian trixie, ubuntu noble, ubuntu resolute, ubuntu stonking

πŸ’– Test::Roo β€” the manual, but make it cute πŸ’–

okay bestie, so Test::Roo(3pm) is a Perl module and honestly? it's giving composable, reusable tests with roles and Moo. that's literally the NAME line, i'm not even playing. version 1.004, periodt. πŸ’…


✨ SYNOPSIS ✨

so the whole vibe is: you define your test behaviors and required fixtures inside a role:

# t/lib/ObjectCreation.pm

package ObjectCreation;
use Test::Roo::Role;    # loads Moo::Role and Test::More

requires 'class';       # we need this fixture

test 'object creation' => sub {
    my $self = shift;
    require_ok( $self->class );
    my $obj  = new_ok( $self->class );
};

1;

then you provide the fixtures and run the tests from your .t file:

# t/test.t

use Test::Roo; # loads Moo and Test::More
use lib 't/lib';

# provide the fixture
has class => (
    is      => 'ro',
    default => sub { "Digest::MD5" },
);

# specify behaviors to test
with 'ObjectCreation';

# give our subtests a pretty label
sub _build_description { "Testing " . shift->class }

# run the test with default fixture
run_me;

# run the test with different fixture
run_me( { class => "Digest::SHA1" } );

done_testing;

and the result? iconic:

$ prove -lv t
t/test.t ..
        ok 1 - require Digest::MD5;
        ok 2 - The object isa Digest::MD5
        1..2
    ok 1 - object creation
    1..1
ok 1 - Testing Digest::MD5
        ok 1 - require Digest::SHA1;
        ok 2 - The object isa Digest::SHA1
        1..2
    ok 1 - object creation
    1..1
ok 2 - Testing Digest::SHA1
1..2
ok
All tests successful.
Files=1, Tests=2,  0 wallclock secs ( 0.02 usr  0.01 sys +  0.06 cusr  0.00 csys =  0.09 CPU)
Result: PASS

🌸 DESCRIPTION 🌸

so basically this module lets you compose Test::More tests out of roles. it was inspired by the excellent Test::Routine module, but it uses Moo instead of Moose β€” which means you get most of the benefits without dragging Moose in as a test dependency. slay, honestly.

your test files are Moo classes. you can define any test fixtures you need as Moo attributes. you define tests as method modifiers β€” conceptually similar to subtest in Test::More, except your test method gets passed the test object so it can access fixture attributes. and you can compose any Moo::Role into your test to define attributes, require particular methods, or define tests. πŸ’–

the way this works means you can isolate test behaviors into roles that require certain test fixtures in order to run. your main test file provides the fixtures and composes the roles to run. it makes reusing test behaviors so easy, no cap.

for example, if you're creating tests for Awesome::Module, you could create the test behaviors as Awesome::Module::Test::Role and distribute it with your module. and if another distribution subclasses Awesome::Module, it can compose the Awesome::Module::Test::Role behavior for its own tests. πŸ¦‹

no more copying and pasting tests from a super class! superclasses define and share their tests. subclasses provide their own fixtures and run the tests. that's the whole dream, fr fr.


πŸŽ€ USAGE πŸŽ€

importing Test::Roo also loads Moo (which gives you strictures with fatal warnings and other goodies) and makes the current package a subclass of Test::Roo::Class.

importing also loads Test::More. no test plan is used. the done_testing function must be used at the end of every test file. any import arguments are passed through to Test::More's import method.

see also Test::Roo::Role for test role usage. 🫢

🧚 Creating fixtures

you can create fixtures with normal Moo syntax. you can even make them lazy if you want:

has fixture => (
    is => 'lazy'
);

sub _build_fixture { ... }

this becomes really useful with Test::Roo::Role. a role could define the attribute and require the builder method to be provided by the main test class. lowkey genius.

πŸŽ€ Composing test roles

you can use roles to define units of test behavior and then compose them into your test class using the with function. test roles may define attributes, declare tests, require certain methods and anything else you can regularly do with roles.

use Test::Roo;

with 'MyTestRole1', 'MyTestRole2';

see Test::Roo::Role and the Test::Roo::Cookbook for details and examples. ✨

πŸ’… Setup and teardown

you can add method modifiers around the setup and teardown methods and these will be run before tests begin and after tests finish (respectively).

before  setup     => sub { ... };

after   teardown  => sub { ... };

you can also add method modifiers around each_test, which will be run before and after every individual test. you could use these to prepare or reset a fixture.

has fixture => ( is => 'lazy, clearer => 1, predicate => 1 );

after  each_test => sub { shift->clear_fixture };

roles may also modify setup, teardown, and each_test, so the order that modifiers will be called depends on when roles are composed. be careful with each_test, though β€” the global effect may make composition more fragile. πŸ™ˆ

you can call test functions in modifiers. for example, you could confirm that something has been set up or cleaned up.

before each_test => sub { ok( ! shift->has_fixture ) };

🌈 Running tests

the simplest way to use Test::Roo with a single .t file is to let the main package be the test class and call run_me in it:

# t/test.t
use Test::Roo; # loads Moo and Test::More

has class => (
    is      => 'ro',
    default => sub { "Digest::MD5" },
);

test 'load class' => sub {
    my $self = shift;
    require_ok( $self->class );
}

run_me;
done_testing;

calling run_me(@args) is equivalent to calling __PACKAGE__->run_tests(@args) and runs tests for the current package.

you may specify an optional description or hash reference of constructor arguments to customize the test object:

run_me( "load MD5" );
run_me( { class => "Digest::MD5" } );
run_me( "load MD5", { class => "Digest::MD5" } );

see Test::Roo::Class for more about the run_tests method.

alternatively, you can create a separate package (in the test file or in a separate .pm file) and run tests explicitly on that class.

# t/test.t
package MyTest;
use Test::Roo;

use lib 't/lib';

has class => (
    is       => 'ro',
    required => 1,
);

with 'MyTestRole';

package main;
use strictures;
use Test::More;

for my $c ( qw/Digest::MD5 Digest::SHA/ ) {
    MyTest->run_tests("Testing $c", { class => $c } );
}

done_testing;

πŸ‘‘ EXPORTED FUNCTIONS πŸ‘‘

loading Test::Roo exports subroutines into the calling package to declare and run tests. cute, right?

⭐ test

test $label => sub { ... };

the test function adds a subtest. the code reference will be called with the test object as its only argument.

tests are run in the order declared, so the order of tests from roles will depend on when they are composed relative to other test declarations. keep that in mind bestie.

πŸ”₯ top_test

top_test $label => sub { ... };

the top_test function adds a "top level" test. works exactly like test except it will not start a subtest. this is especially useful in very simple testing situations where the extra subtest level is just noise.

so for example the following test

# t/test.t
use Test::Roo;

has class => (
    is       => 'ro',
    required => 1,
);

top_test basic => sub {
    my $self = shift;

    require_ok($self->class);
    isa_ok($self->class->new, $self->class);
};

for my $c ( qw/Digest::MD5 Digest::SHA/ ) {
    run_me("Testing $c", { class => $c } );
}

done_testing;

produces the following TAP

t/test.t ..
    ok 1 - require Digest::MD5;
    ok 2 - The object isa Digest::MD5
    1..2
ok 1 - Testing Digest::MD5
    ok 1 - require Digest::SHA1;
    ok 2 - The object isa Digest::SHA1
    1..2
ok 2 - Testing Digest::SHA1
1..2
ok
All tests successful.
Files=1, Tests=2,  0 wallclock secs ( 0.02 usr  0.01 sys +  0.06 cusr  0.00 csys =  0.09 CPU)
Result: PASS

πŸ’œ run_me

run_me;
run_me( $description );
run_me( $init_args   );
run_me( $description, $init_args );

the run_me function calls the run_tests method on the current package and passes all arguments to that method. it takes a description and/or a hash reference of constructor arguments.


πŸ¦‹ DIFFERENCES FROM TEST::ROUTINE πŸ¦‹

okay so while this module was inspired by Test::Routine, it is not a drop-in replacement. here's the tea β€” an overview of major differences:

  • Test::Roo uses Moo; Test::Routine uses Moose
  • loading Test::Roo makes the importing package a class; in Test::Routine it becomes a role
  • loading Test::Roo loads Test::More; Test::Routine does not
  • in Test::Roo, run_test is a method; in Test::Routine it is a function and takes arguments in a different order
  • in Test::Roo, all role composition must be explicit using with; in Test::Routine, the run_tests command can also compose roles
  • in Test::Roo, test blocks become method modifiers hooked on an empty method; in Test::Routine, they become methods run via introspection
  • in Test::Roo, setup and teardown are done by modifying setup and teardown methods; in Test::Routine they are done by modifying run_test

πŸ’€ SUPPORT πŸ’€

πŸ“ Bugs / Feature Requests

please report any bugs or feature requests through the issue tracker at <https://github.com/dagolden/Test-Roo/issues>. you will be notified automatically of any progress on your issue. 🫢

🧚 Source Code

this is open source software. the code repository is available for public review and contribution under the terms of the license.

<https://github.com/dagolden/Test-Roo>

git clone https://github.com/dagolden/Test-Roo.git

🌟 AUTHOR 🌟

David Golden <dagolden@cpan.org>

🌈 CONTRIBUTORS 🌈

  • Arthur Axel 'fREW' Schmidt <frioux@gmail.com>
  • Diab Jerius <djerius@gmail.com>

this software is Copyright (c) 2013 by David Golden.

this is free software, licensed under:

The Apache License, Version 2.0, January 2004

aaaand that's the whole page bestie πŸ’– this doc came from perl v5.34.0, dated 2022-06-28, and that's on Test::Roo(3pm) β€” go forth and compose your tests, you absolute icon βœ¨πŸŽ€πŸ’…

πŸ“¦ where this page comes from