O'Reilly logo

Perl Testing: A Developer's Notebook by Chromatic, Ian Langworth

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Improving Test Comparisons

ok() may be the basis of all testing, but it can be inconvenient to have to reduce every test in your system to a single conditional expression. Fortunately, Test::More provides several other testing functions that can make your work easier. You’ll likely end up using these functions more often than ok().

This lab demonstrates how to use the most common testing functions found in Test::More.

How do I do that?

The following listing tests a class named Greeter, which takes the name and age of a person and allows her to greet other people. Save this code as greeter.t:

    #!perl

    use strict;
    use warnings;

    use Test::More tests => 4;

    use_ok( 'Greeter' ) or exit;

    my $greeter = Greeter->new( name => 'Emily', age => 21 );
    isa_ok( $greeter, 'Greeter' );

    is(   $greeter->age(),   21,
        'age() should return age for object' );
    like( $greeter->greet(), qr/Hello, .+ is Emily!/,
        'greet() should include object name' );

Note

The examples in "Writing Your First Test,” earlier in this chapter, will work the same way if you substitute Test::More for Test::Simple; Test::More is a superset of Test:: Simple.

Now save the module being tested in your library directory as Greeter.pm:

    package Greeter;

    sub new
    {
        my ($class, %args) = @_;
        bless \%args, $class;
    }

    sub name
    {
        my $self = shift;
        return $self->{name};
    }

    sub age
    {
        my $self = shift;
        return $self->{age};
    }

    sub greet
    {
        my $self = shift;
        return "Hello, my name is " . $self->name() . "!";
    }

    1;

Running the file from the command line with prove should reveal three successful tests:

    $ prove greeter.t
    greeter.t....ok
    All tests successful.
    Files=1, Tests=4,  0 wallclock secs ( 0.07 cusr +  0.03 csys =  0.10 CPU)

What just happened?

This program starts by loading the Greeter module and creating a new Greeter object for Emily, age 21. The first test checks to see if the constructor returned an actual Greeter object. isa_ok() performs several checks to see if the variable is actually a defined reference, for example. It fails if it is an undefined value, a non-reference, or an object of any class other than the appropriate class or a derived class.

The next test checks that the object’s age matches the age set for Emily in the constructor. Where a test using Test::Simple would have to perform this comparison manually, Test::More provides the is() function that takes two arguments to compare, along with the test description. It compares the values, reporting a successful test if they match and a failed test if they don’t.

Note

Test::More::is() uses a string comparison. This isn’t always the right choice for your data. See Test::More::cmp_ ok() to perform other comparisons.

Similarly, the final test uses like() to compare the first two arguments. The second argument is a regular expression compiled with the qr// operator. like() compares this regular expression against the first argument—in this case, the result of the call to $greeter->greet()—and reports a successful test if it matches and a failed test if it doesn’t.

Avoiding the need to write the comparisons manually is helpful, but the real improvement in this case is how these functions behave when tests fail. Add two more tests to the file and remember to change the test plan to declare six tests instead of four. The new code is:

    use Test::More tests => 6;

    ...

    is(   $greeter->age(),   22,
        'Emily just had a birthday' );
    like( $greeter->greet(), qr/Howdy, pardner!/,
        '... and she talks like a cowgirl' );

Note

See “Regexp Quote-Like Operators” in perlop to learn more about qr//.

Run the tests again with prove’s verbose mode:

    $ prove -v greeter.t
    greeter.t....1..6
    ok 1 - use Greeter;
    ok 2 - The object isa Greeter
    ok 3 - age() should return age for object
    ok 4 - greet() should include object name
    not ok 5 - Emily just had a birthday
    #     Failed test (greeter.t at line 18)
    #          got: '21'
    #     expected: '22'
    not ok 6 - ... and she talks like a cowgirl
    #     Failed test (greeter.t at line 20)
    #                   'Hello, my name is Emily!'
    #     doesn't match '(?-xism:Howdy, pardner!)'
    # Looks like you failed 2 tests of 6.
    dubious
            Test returned status 2 (wstat 512, 0x200)
    DIED. FAILED tests 5-6
            Failed 2/6 tests, 66.67% okay
    Failed Test Stat Wstat Total Fail  Failed  List of Failed
    ----------------------------------------------------------------------------
    greeter.t      2   512     6    2  33.33%  5-6
    Failed 1/1 test scripts, 0.00% okay. 2/6 subtests failed, 66.67% okay.

Note

The current version of prove doesn’t display the descriptions of failing tests, but it does display diagnostic output.

Notice that the output for the new tests—those that shouldn’t pass—contains debugging information, including what the test saw, what it expected to see, and the line number of the test. If there’s only one benefit to using ok() from Test::Simple or Test::More, it’s these diagnostics.

What about...

Q: How do I test things that shouldn’t match?

A: Test::More provides isnt() and unlike(), which work the same way as is() and like(), except that the tests pass if the arguments do not match. Changing the fourth test to use isnt() and the fifth test to use unlike() will make them pass, though the test descriptions will seem weird.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required