Perl Unit Testing: Tools

G. Wade Johnson

Houston.pm

notes

Unit Testing in Perl

These are the topics I plan to cover today. This won't cover everything you need to know about unit testing in Perl, but it will give you more than the intro talk.

Test Anything Protocol (TAP)

    1..4
    ok 1 - Initial sanity verified
    ok 2 - Sanity still exists
    not ok 3 - Sanity has left the building
    #   Failed test 'Sanity has left the building'
    #   at examples/sanity.t line 11.
    #          got: '4'
    #     expected: '5'
    ok 4 - Sanity has been restored
    # Looks like you failed 1 test of 4.

This is a very simple example of the output from a TAP-based test. It shows all of the major parts of a TAP test.

  • Marker indicating the number of tests to run
  • A series of test assertions.
    • Success indicator: ok or not ok
    • Assertion number
    • Assertion name/label
  • Diagnostic information preceded by a #

Terminology

assertion
A confident and forceful statement of fact or belief.
A condition that must be true for a test to succeed.
test
A set of assertions that specify success of some action.
test suite
A set of tests that exercise and verify a piece of software.

This just sets up some definitions so we will be on the same page when I begin talking about tests. A test usually requires some quantity of setup, the execution of code, and then one or more assertions about the result of the code.

Simplest assertion

The most basic assertion tool: ok()

ok() is the simplest assertion in the Test::More toolkit. If it's first argument is true, it prints ok. Otherwise, it prints not ok.

Using ok

    #!/usr/bin/perl
    use Test::More tests => 4;

    use strict;
    use warnings;

    my $var = 1;
    ok( $var == 1, 'Initial sanity verified' );
    ok( $var =~ m/^\d+$/, 'Sanity still exists' );
    ok( 2+2 == 5, 'Sanity has left the building' );
    ok( 2+2 != 5, 'Sanity has been restored' );

It only supplies the ability to set the test plan and a single function ok(). The ok() function tests a scalar value, printing ok on true and not ok for a false value.

It also provides the assertion number automatically, and adds a label if provided as a second argument.

Output of ok

    1..4
    ok 1 - Initial sanity verified
    ok 2 - Sanity still exists
    not ok 3 - Sanity has left the building
    ok 4 - Sanity has been restored
    # Looks like you failed 1 test of 4.

The ok() function has two related flaws.

  • Lousy at documenting any assertion that isn't a simple boolean.
  • In case of failure, there is no troubleshooting information

As you can see here, you really don't know anything about why assertion 3 failed.

More Expressive Assertions

Test::More provides several wrappers around ok that do a much better job of expressing intent and providing troubleshooting information. These are the most obvious ones.

is() and isnt() use string comparisons. Most of the time that doesn't matter. But, once in a while you need to know.

Usage of Expressive Assertions

    #!/usr/bin/perl
    use Test::More tests => 4;

    use strict;
    use warnings;

    my $var = 1;
    is( $var, 1, 'Initial sanity verified' );
    like( $var, qr/^\d+$/, 'Sanity still exists' );
    is( 2+2, 5, 'Sanity has left the building' );
    isnt( 2+2, 5, 'Sanity has been restored' );

These assertions are more expressive. These don't really do much more than the straight ok() function if the assertion succeeds. But, if the assertion fails, they provide more useful diagnostic output.

Output of Expressive Assertions

    1..4
    ok 1 - Initial sanity verified
    ok 2 - Sanity still exists
    not ok 3 - Sanity has left the building
    #   Failed test 'Sanity has left the building'
    #   at examples/sanity.t line 11.
    #          got: '4'
    #     expected: '5'
    ok 4 - Sanity has been restored
    # Looks like you failed 1 test of 4.

As you can see, the failure condition actually gives useful output this time. If I had given actual useful names to the assertions, you might even be able to tell what the problem is from here.

Misused Test::More Assertions

These assertions are not nearly as useful as they appear at first glance. They attempt to use (or require) the specified module. A successful load passes the assertion and unsuccessful load, fails. This seems relatively straight-forward and reasonable.

The two points that fall down are:

  • use_ok happens at runtime, unlike use.
  • On failure, the test file continues executing. Which will likely generate a large number of extra assertion failures.

More Assertions

Use of Interesting Assertions

    #!/usr/bin/perl
    use Test::More tests => 4;

    use strict;
    use warnings;

    my %hash = qw/c 3 b 2 a 1/;
    is_deeply( {a=>1, b=>2, c=>3}, \%hash, 'hash items' );
    cmp_ok( 4, '<', 5, 'integer ordering' );
    isa_ok( \%hash, 'HASH', '%hash' );
    can_ok( 'Test::More', qw/ok is isnt like unlike/ );

Use of all of these are pretty much as you might expect.

Displaying More Information

These are sometimes used to display information that the maintainer of the suite may find interesting. Unfortunately, this information is not always as useful the 400th time you see it.

A Quick Aside

Each assertion returns a true value on success and false on failure.

This is a feature of the assertions that can sometimes be exploited to make your test suites even more useful.

Cool Troubleshooting Trick

    #!/usr/bin/perl
    use Test::More tests => 1;

    use strict;
    use warnings;

    my %hash = qw/c 3 b 2 a 1/;
    # Pretend that $output came from a function under test.
    my $output = {a=>1, b=>2, c=>3};
    is_deeply( $output, \%hash, 'hash items' )
        or note explain $output;

The or note explain idiom is much more useful than what we normally do.

Troubleshoot the Hard Way

notes

Troubleshoot with or note explain

notes

How to Name Tests

notes

Unique

notes

Expressive

notes

Expected Behavior

Don't assume the person troubleshooting a failure knows why you are making this assertion.

notes

More Test Modules

Testing Patterns

When thinking about unit testing at a high level, you being to see certain patterns and practices emerge.

Levels of Failure

Skipping Tests

    #!/usr/bin/perl
    use Test::More tests => 4;

    use strict;
    use warnings;

    SKIP: {
        skip 'Root access needed', 2 if $< != 0;
        ok( do_root_action(), 'Root does action' );
        is( get_root_information(), 'root stuff',
            'Root gets info' );
    }
    ok( perform_unprivileged(), 'Any user action' );
    is( get_information(), 'unprivileged',
        'Get normal information' );

Sometimes you need to run tests in only certain circumstances. The skip function allows you to bypass assertions, treating them as successful for the sake of the test suite.

Skipping Test File

    #!/usr/bin/perl
    use Test::More ($^O =~ /win32/i)
          ? (skip_all => 'Cannot test under windows')
          : (tests => 4));

If all of the assertions in a given file must be bypassed for the same reason, skip_all is the better tool.

Automated TODOs

    #!/usr/bin/perl
    use Test::More tests => 4;

    use strict;
    use warnings;

    ok( working_method(), 'this works' );
    TODO: {
        local $TODO = 'foo method is not finished';

        ok( !foo(), 'foo with no arguments' );
        is_deeply( foo(qw/a b c/), [qw/C B A/],
            'foo with arguments' );
    }
    ok( other_working_method(), 'this does too' );

These tests serve as reminders that we need to write more functionality. However, they are not counted as failures, even though the assertions do not succeed. Just as importantly, they also report if they begin functioning.

Planning

The no_plan option is a really bad idea. You can accidentally bypass or miss assertions without any indication from the test harness. The done_testing option is safer and becoming more popular. However, it also allows the possibility of skipped assertions without any indication.

Explicitly setting the number of tests is the safest option. But it is more work to keep it properly configured when building your test file.