NAME

Test::Routine::Manual::Demo - a walkthrough, in code, of Test::Routine

VERSION

version 0.015

The Demo

t/demo/01-demo.t

#!/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;

t/demo/02-simple.t

# Welcome to part two of the Test::Routine demo.  This is showing how you can
# write quick one-off tests without having to write a bunch of .pm files or
# (worse?) embed packages in bare blocks in the odious way that 01-demo.t did.
#
# First off, we use Test::Routine.  As it did before, this turns the current
# package (main!) into a Test::Routine role.  It also has the pleasant
# side-effect of turning on strict and warnings.
use Test::Routine;

# Then we bring in the utils, because we'll want to run_tests later.
use Test::Routine::Util;

# And, finally, we bring in Test::More so that we can use test assertions, and
# namespace::autoclean to clean up after us.
use Test::More;
use namespace::autoclean;

# We're going to give our tests some state.  It's nothing special.
has counter => (
  is  => 'rw',
  isa => 'Int',
  default => 0,
);

# Then another boring but useful hunk of code: a method for our test routine.
sub counter_is_even {
  my ($self) = @_;
  return $self->counter % 2 == 0;
}

# Then we can write some tests, just like we did before.  Here, we're writing
# several tests, and they will be run in the order in which they were defined.
# You can see that they rely on the state being maintained.
test 'start even' => sub {
  my ($self) = @_;
  ok($self->counter_is_even, "we start with an even counter");

  $self->counter( $self->counter + 1);
};

test 'terminate odd' => sub {
  my ($self) = @_;

  ok(! $self->counter_is_even, "the counter is odd, so state was preserved");
  pass("for your information, the counter is " . $self->counter);
};

# Now we can run these tests just by saying "run_me" -- rather than expecting a
# class or role name, it uses the caller.  In this case, the calling package
# (main!) is a Test::Routine, so the runner composes it with Moose::Object,
# instantiating it, and running the tests on the instance.
run_me;

# Since each test run gets its own instance, we can run the test suite again,
# possibly to verify that the test suite is not destructive of some external
# state.
run_me("second run");

# And we can pass in args to use when constructing the object to be tested.
# Given the tests above, we can pick any starting value for "counter" that is
# even.
run_me({ counter => 192 });

# ...and we're done!
done_testing;

# More Test::Routine behavior is demonstrated in t/03-advice.t and t/04-misc.t
# Go have a look at those!

t/demo/03-advice.t

use Test::Routine;
use Test::Routine::Util;
use Test::More;

use namespace::autoclean;

# xUnit style testing has the idea of setup and teardown that happens around
# each test.  With Test::Routine, we assume that you will do most of this sort
# of thing in your BUILD, DEMOLISH, and attribute management.  Still, you can
# easily do setup and teardown by applying method modifiers to the "run_test"
# method, which your Test::Routine uses to run each test.  Here's a simple
# example.

# We have the same boring state that we saw before.  It's just an integer that
# is carried over between tests.
has counter => (
  is   => 'rw',
  isa  => 'Int',
  lazy => 1,
  default => 0,
  clearer => 'clear_counter',
);

# The first test changes the counter's value and leaves it changed.
test test_0 => sub {
  my ($self) = @_;

  is($self->counter, 0, 'start with counter = 0');
  $self->counter( $self->counter + 1);
  is($self->counter, 1, 'end with counter = 1');
};

# The second test assumes that the value is the default, again.  We want to
# make sure that before each test, the counter is reset, but we don't want to
# tear down and recreate the whole object, because it may have other, more
# expensive resources built.
test test_1 => sub {
  my ($self) = @_;

  is($self->counter, 0, 'counter is reset between tests');
};

# ...so we apply a "before" modifier to each test run, calling the clearer on
# the counter.  When next accessed, it will re-initialize to zero.  We could
# call any other code we want here, and we can compose numerous modifiers
# together onto run_test.
#
# If you want to clear *all* the object state between each test... you probably
# want to refactor.
before run_test => sub { $_[0]->clear_counter };

run_me;
done_testing;

t/demo/04-misc.t

use Test::Routine;
use Test::Routine::Util;
use Test::More;

use namespace::autoclean;

# One thing that the previous examples didn't show was how to mark tests as
# "skipped" or "todo."  Test::Routine makes -no- provisions for these
# directives.  Instead, it assumes you will use the entirely usable mechanisms
# provided by Test::More.

# This is a normal test.  It is neither skipped nor todo.
test boring_ordinary_tests => sub {
  pass("This is a plain old boring test that always passes.");
  pass("It's here just to remind you what they look like.");
};

# To skip a test, we just add a "skip_all" plan.  Because test methods get run
# in subtests, this skips the whole subtest, but nothing else.
test sample_skip_test => sub {
  plan skip_all => "these tests don't pass, for some reason";

  is(6, 9, "I don't mind.");
};

# To mark a test todo, we just set our local $TODO variable.  Because the test
# is its own block, this works just like it would in any other Test::More test.
test sample_todo_test => sub {
  local $TODO = 'demo of todo';

  is(2 + 2, 5, "we can bend the fabric of reality");
};

run_me;
done_testing;

t/demo/05-multiple.t

#!/bin/env perl
use strict;
use warnings;

use Test::Routine::Util;
use Test::More;

# One of the benefits of building our sets of tests into roles instead of
# classes is that we can re-use them in whatever combination we want.  We can
# break down sets of tests into bits that can be re-used in different cases.
# With classes, this would lead to multiple inheritance or other monstrosities.

# Here's a first Test::Routine.  We use it to make sure that one of our
# fixture's attributes is a numeric id.
{
  package Test::ThingHasID;
  use Test::Routine;
  use Test::More;

  requires 'id';

  test thing_has_numeric_id => sub {
    my ($self) = @_;

    my $id = $self->id;
    like($id, qr/\A[0-9]+\z/, "the thing's id is a string of ascii digits");
  };
}

# A second one ensures that the thing has an associated directory that
# looks like a unix path.
{
  package Test::HasDirectory;
  use Test::Routine;
  use Test::More;

  requires 'dir';

  test thing_has_unix_dir => sub {
    my ($self) = @_;

    my $dir = $self->dir;
    like($dir, qr{\A(?:/\w+)+/?\z}, "thing has a unix-like directory");
  };
}

# We might have one class that is only expected to pass one test:
{
  package JustHasID;
  use Moose;

  has id => (
    is      => 'ro',
    default => sub { 
      my ($self) = @_;
      return Scalar::Util::refaddr($self);
    },
  );
}

# ...and another class that should pass both:
{
  package UnixUser;
  use Moose;

  has id  => (is => 'ro', default => 501);
  has dir => (is => 'ro', default => '/home/users/rjbs');
}

# So far, none of this is new, it's just a slightly different way of factoring
# things we've seen before.  In t/01-demo.t, we wrote distinct test roles and
# classes, and we made our class compose the role explicitly.  This can be
# a useful way to put these pieces together, but we also might want to write
# all these classes and roles as unconnected components and compose them only
# when we're ready to run our tests.  When we do that, we can tell run_tests
# what to put together.
#
# Here, we tell it that we can test JustHasID with Test::ThingHasID:
run_tests(
  "our JustHasID objects have ids",
  [ 'JustHasID', 'Test::ThingHasID' ],
);

# ...but we can run two test routines against our UnixUser class
run_tests(
  "unix users have dirs and ids",
  [ 'UnixUser', 'Test::ThingHasID', 'Test::HasDirectory' ],
);


# We can still use the "attributes to initialize an object," and when doing
# that it may be that we don't care to run all the otherwise applicable tests,
# because they're not interesting in the scenario we're creating.  For
# example...
run_tests(
  "a trailing slash is okay in a directory",
  [ 'UnixUser', 'Test::HasDirectory' ],
  { dir => '/home/meebo/' },
);

# ...and we're done!
done_testing;

AUTHOR

Ricardo Signes <rjbs@cpan.org>

COPYRIGHT AND LICENSE

This software is copyright (c) 2010 by Ricardo Signes.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.