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

NAME

Test::Functional - Perl tests in a functional style.

SYNOPSIS

  use Test::Functional;

  # make sure the bomb goes off
  sub explode { die "BOOM" }
  test { explode() } dies, "test-3";

  # implicit and explicit equivalence
  test { 2 * 2 } 4, "test-1";
  test { 2 * 2 } eqv 4, "test-1";

  # test blocks can be as simple or as involved as you want
  test { 3 > 0 } true, "test-4";
  test {
      my $total = 0;
      foreach my $person ($car->occupants) {
          $total += $person->weight
      }
      $total < 600
  } true, "test-5";

  # after the test runs, you also get the result.
  my $horse = test { Horse->new } typeqv "Horse", "test-6";

  # you can make your own comparator functions, or use existing ones.
  use Test::More import => [qw(like)];
  sub islike {
      my ($other) = @_;
      return sub {
          my ($got, $testname) = @_;
          like($got, $other, $testname);
      };
  }
  test { 'caterpillar' } islike(qr/cat/), 'is cat?';

DESCRIPTION

This modules uses (abuses?) the ability to create new syntax via perl prototypes to create a testing system focused on functions rather than values. Tests run blocks of Perl, and use comparator functions to test the output. Despite being a different way of thinking about tests, it plays well with Test::More and friends.

EXPORTS

Since this module is going to be used for test scripts, its methods all export by default. You can choose which you want using the standard directives:

    # import only eqv
    use Test::Functional tests => 23, import => ['eqv'];

    # import all but notest
    use Test::Functional tests => 23, import => ['!notest'];

CONFIGURE

This package has two settings which can be altered to change performance:

    unstable - run tests which are normally skipped
    fastout  - cause the entire test to end after the first failure

This package can be configured via Test::Functional::Conf or the configure() function.

configure KEY => VALUE, ...

Changes configuration values at run-time.

TEST STRUCTURES

test { BLOCK } [CONDITION,] NAME

This is the basic building block of Test::Functional. Each test function contains an anonymous code block (which is expected to return a scalar result), a name for the test, and a condition (an optional subroutine to check the result).

In most cases, a test passes if the code block doesn't die, and if the condition is true (or absent). There is a special condition dies which expects the code block to die, and fails unless it does so.

Whether the test passes or fails, test returns the value generated by BLOCK.

pretest { BLOCK } [CONDITION,] NAME

This works like test except that if it fails, it will short-circuit all testing at the current level. This means that top-level pretest calls will halt the entire test if they fail. One obvious example for this is:

    BEGIN { pretest { use Foo::Bar } "test-use" }
    test { Foo::Bar::double(2) } eqv(4), "double(2)";
    test { Foo::Bar::double(3) } eqv(6), "double(3)";
    test { Foo::Bar::double(4) } eqv(8), "double(4)";

If the use Foo::Bar fails, the information that all the other tests are failing is less useful. pretest can also be combined with group (described later) to short-circuit a small set of related tests.

notest { BLOCK } [CONDITION,] NAME

This is has exactly the same semantics as test; the only difference is that it normally doesn't run. If Test::Functional::Conf->unstable is true, then this test will run, otherwise it won't, and will just return undef.

For test-driven development, it is useful to create failing tests using notest blocks; this prevents test regression. Once the implementation starts working notest can be switched to test.

group { BLOCK } NAME

Groups are blocks which wrap associated tests. Groups can be used to namespace tests as well as to allow groups of tests to fail together. Here is a short example:

    group {
        my $a = coretest { Adder->new } typeqv 'Adder', "new";

        test { $a->add(4, 6) } 10, "4 + 6";
        test { $a->add("cat", "dog") } dies, "mass hysteria";
        test { $a->add() } isundef, "not a number";

    } "adder";

If Adder->new fails, the rest of the tests aren't producing useful results, so they will be skipped. See the ETHOS section for a more in-depth discussion of the package in general, and the implications of test short-circuiting in particular.

TEST CONDITIONS

eqv OBJECT

Creates a function which tests that the result is exactly equivalent (eqv) to OBJECT (using Test::More::is_deeply). It works for both simple values and nested data structures. See Test::More for more details.

If test receives a condition which isn't a code-ref, it will be wrapped in an eqv call, since this is the most common case (testing that a result is the expected value).

ineqv OBJECT

Tests whether the result differs from (is inequivalent to) OBJECT according to Data::Compare. This is expected (hoped?) to be inverse of eqv.

typeqv TYPE

Creates a function which tests that the result is of (or inhereits from) the provided TYPE (that the result's type is equivalent to TYPE). For unblessed references, it checks that ref($result) eq $type. For blessed references it checks that $result->isa($type). Results which are not references will always be false.

dies

Verifies that the test's code block died. It is unique amongst test conditions in that it doesn't test the result, but rather tests $@. Any result other than a die succeeds.

noop

This is the "default" condition; if no condition is given to a test then this condition is used. As long as the code block does not die, the test passes.

true

Verifies that the result is a true value.

false

Verifies that the result is a false value.

isdef

Checks that the result is defined (not undef).

isundef

Checks that the result is undefined.

CUSTOM TEST CONDITIONS

Anonymous subroutines can be used in place of the provided test conditions. These functions take two arguments: the test result and the test's name. Here are some examples:

  use Test::More;

  sub over21 {
      my ($result, $name) = @_;
      return cmp_ok($result, '>=', 21, $name);
  }
  test { $alice->age } \&over21, 'can alice drink?';
  test { $bob->age } \&over21, 'can bob drink?';

These examples are kind of clunky, but you get the idea. Using anything complicated will probably require reading the source, and/or learning how to use Test::Builder. In particular, it's important to make sure builder->level is set correctly.

ETHOS

This package exists to address some specific concerns I've had while writing tests using other frameworks. As such, it has some pretty major differences from the other testing frameworks out there.

Most Perl tests are written as perl scripts which test Perl code by calling functions or methods, and then using various Test packages to look at the result. This approach has some problems:

  1. Test scripts can make bad assumptions or have bugs, causing problems that aren't obviously linked to a particular test clause and which can be hard to track down and fix.

  2. Writing defensive test scripts involves a bunch of relatively boiler-plate eval-blocks and $@ tests, as well as effectively doubling the number of tests that are "run" without meaningfully doubling the test coverage.

  3. In some cases a small early error causes tons of test clauses to spew useless messages about failing; this loses sight of the basic issue that caused the problem (syntax error, missing module, etc).

Test::Functional addresses these concerns: it enables the programmer to write all the "meat" of the test script inside anonymous subs which are tests [1]. Since each test checks both that the code did not die and that the result was what was expected, the tester doesn't have to worry about what kind of failure might occur, just about the expected outcome [2]. Especially when trying to test other people's code (gray box testing?) this feature is invaluable.

The various features to prematurely end the test (using pretest() and/or $Test::Functional::Conf->fastout) can help the developer to focus on the problem at hand, rather than having to filter through spew [3]. This is especially nice during test-driven development, or when trying to increase coverage for an old and crufty module.

AUTHOR

Erik Osheim <erik at osheim.org>

BUGS

The syntax takes some getting used to.

I should create default wrappers for things such as like and compare from Test::More. Currently I mostly use true but that gives less debugging information.

I wrote these tests to suit my needs, so I am sure there are cases I haven't thought of or encountered. Also, I'm sure I have a lot to learn about the intricacies of Test::Harness and Test::Module. Please contact me (via email or http://rt.cpan.org) with any comments, advice, or problems.

ACKNOWLEDGEMENTS

This module is based on Test::Builder::Module, and relies heavily on the work done by Michael Schwern. It also uses Data::Compare by David Cantrell.

COPYRIGHT & LICENSE

Copyright 2009 Erik Osheim, all rights reserved.

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