#!/bin/env perl use strict; use warnings; # This test is both a test and an example of how Test::Routine works! Welcome # to t/01-demo.t, I will be your guide, rjbs. { # This block defines the HashTester package. It's a Test::Routine, meaning # it's a role. We define state that the test will need to keep and any # requirements we might have. # # Before we can run this test, we'll need to compose the role into a class so # that we can make an instance. package HashTester; use Test::Routine; # We import stuff from Test::More because, well, who wants to re-write all # those really useful test routines that exist out there? Maybe somebody, # but not me. use Test::More; # ...but then we use namespace::autoclean to get rid of the routines once # we've bound to them. This is just standard Moose practice, anyway, right? use namespace::autoclean; # Finally, some state! Every test will get called as method on an instance, # and it will have this attribute. Here are some points of interest: # # - We're giving this attribute a builder, so it will try to get built with a # call to $self->build_hash_to_test -- so each class that composes this # role can provide means for these attributes (fixtures) to be generated as # needed. # # - We are not adding "requires 'build_hash_to_test'", because then we can # apply this role to Moose::Object and instantiate it with a given value # in the constructor. There will be an example of this below. This lets # us re-use these tests in many variations without having to write class # after class. # # - We don't use lazy_build because it would create a clearer. If someone # then cleared our lazy_build fixture, it could not be re-built in the # event that we'd gotten it explicitly from the constructor! # # Using Moose attributes for our state and fixtures allows us to get all of # their powerful behaviors like types, delegation, traits, and so on, and # allows us to decompose shared behavior into roles. # has hash_to_test => ( is => 'ro', isa => 'HashRef', builder => 'build_hash_to_test', ); # Here, we're just declaring an actual test that we will run. This sub will # get installed as a method with a name that won't get clobbered easily. The # method will be found later by run_tests so we can find and execute all # tests on an instance. # # There is nothing magical about this method! Calling this method is # performed in a Test::More subtest block. A TAP plan can be issued with # "plan", and we can issue TODO or SKIP directives the same way. There is # none of the return-to-skip magic that we find in Test::Class. # # The string after "test" is used as the method name -- which means we're # getting a method name with spaces in it. This can be slightly problematic # if you try to use, say, ::, in a method name. For the most part, it works # quite well -- but look at the next test for an example of how to give an # explicit description. test "only one key in hash" => sub { my ($self) = @_; my $hash = $self->hash_to_test; is(keys %$hash, 1, "we have one key in our test hash"); is(2+2, 4, "universe still okay"); }; # The only thing of note here is that we're passing a hashref of extra args # to the test method constructor. "desc" lets us set the test's description, # which is used in the test output, so we can avoid weird method names being # installed. Also note that we order tests more or less by order of # definition, not by name or description. test second_test => { desc => "Test::Routine demo!" } => sub { pass("We're running this test second"); pass("...notice that the subtest's label is the 'desc' above"); pass("...and not the method name!"); }; } { # This package is one fixture against which we can run the HashTester # routine. It has the only thing it needs: a build_hash_to_test method. # Obviously real examples would have more to them than this. package ProcessHash; use Moose; with 'HashTester'; use namespace::autoclean; sub build_hash_to_test { return { $$ => $^T } } } # Now we're into the body of the test program: where tests actually get run. # We use Test::Routine::Util to get its "run_tests" routine, which runs the # tests on an instance, building it if needed. use Test::Routine::Util; # We use Test::More to get done_testing. We don't assume that run_tests is the # entire test, because that way we can (as we do here) run multiple test # instances, and can intersperse other kinds of sanity checks amongst the # Test::Routine-style tests. use Test::More; is(2+2, 4, "universe still makes sense") or BAIL_OUT("PANIC!"); # The first arg is a description for the subtest that will be run. The second, # here, is a class that will be instantiated and tested. run_tests('ProcessHash class' => 'ProcessHash'); # Here, the second argument is an instance of a class to test. run_tests('ProcessHash obj' => ProcessHash->new({ hash_to_test => { 1 => 0 }})); # We could also just supply a class name and a set of args to pass to new. # The below is very nearly equivalent to the above: run_tests('ProcessHash new' => ProcessHash => { hash_to_test => { 1 => 0 }}); # ...and here, the second arg is not a class or instance at all, but the # Test::Routine role (by name). Since we know we can't instantiate a role, # run_tests will try to compose it with Moose::Object. Then the args are used # as the args to ->new on the new class, as above. This lets us write # Test::Routines that can be tested with the right state to start with, or # Test::Routines that need to be composed with testing fixture classes. run_tests( 'HashTester with given state', HashTester => { hash_to_test => { a => 1 }, }, ); # There's one more interesting way to run out tests, but it's demonstrated in # 02-simple.t instead of here. Go check that out. # ...and we're done! done_testing;