Fork me on GitHub

Project Notes

#458 Test::More

The basics of Perl testing with the Test::More module.

Notes

Test::More is the standard Perl module for writing test scripts. It provides a wide array of utilities to verify code behavior and output diagnostic information in the Test Anything Protocol (TAP) format.

Test::More is a direct upgrade to Test::Simple. They both output the same Test Anything Protocol (TAP) format, but Test::More provides significantly better tools for debugging. See also: LCK#454 Test::Simple

Core Features

  • Simple Assertions: basic checks like ok($condition, $name) for truthiness and is($got, $expected, $name) for equality.
  • Deep Comparison: is_deeply($got, $expected, $name) tests complex, nested data structures (like arrays of hashes).
  • Planning: declare how many tests expected to run with tests => $n or use done_testing() at the end of the script to ensure a test didn’t exit prematurely.
  • Diagnostics: When a test fails, Test::More automatically prints “got” and “expected” values to help debugging.
  • Todo & Skip: mark tests as TODO for features not yet implemented or SKIP if certain requirements (like a specific OS or database) aren’t met.

Module installation

$ cpan Test::More
Loading internal logger. Log::Log4perl recommended for better logging
Reading '/Users/paulgallagher/.cpan/Metadata'
  Database was generated on Wed, 27 May 2026 07:54:20 GMT
Test::More is up to date (1.302219).

A Basic Test File

Given a simple script hello.pl:

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

sub hello_world {
  return "Hello, World!";
}

print hello_world(), "\n" if !caller();

1;

When run interactively, it simply prints the result of the hello_world function:

$ ./hello.pl
Hello, World!

Let’s write some tests in hello.t:

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

use lib ".";
require 'hello.pl';

use Test::More tests => 1;

is(hello_world(), 'Hello, World!', 'hello_world output should be sane');

NB: if we don’t know or care how many tests to expect, can declare with no plan: use Test::Simple 'no_plan';

Tests are run with prove:

$ prove hello.t
hello.t .. ok
All tests successful.
Files=1, Tests=1,  0 wallclock secs ( 0.00 usr  0.00 sys +  0.01 cusr  0.00 csys =  0.01 CPU)
Result: PASS
$ prove -v hello.t
hello.t ..
1..1
ok 1 - hello_world output should be sane
ok
All tests successful.
Files=1, Tests=1,  0 wallclock secs ( 0.01 usr  0.01 sys +  0.01 cusr  0.00 csys =  0.03 CPU)
Result: PASS

Too Few Tests

The script too_few_tests.t demonstrates what happens if the test plan is not met.

use Test::More tests => 3;

pass( 'one test' );
pass( 'two tests' );

When executed:

$ prove too_few_tests.t
too_few_tests.t .. 1/3 # Looks like you planned 3 tests but ran 2.
too_few_tests.t .. Dubious, test returned 255 (wstat 65280, 0xff00)
Failed 1/3 subtests

Test Summary Report
-------------------
too_few_tests.t (Wstat: 65280 (exited 255) Tests: 2 Failed: 0)
  Non-zero exit status: 255
  Parse errors: Bad plan.  You planned 3 tests but ran 2.
Files=1, Tests=2,  0 wallclock secs ( 0.00 usr  0.00 sys +  0.01 cusr  0.00 csys =  0.01 CPU)
Result: FAIL

Testing Warnings

Use the Test::Warn extension to test methods for warnings.

Installation:

$ cpan Test::Warn
Loading internal logger. Log::Log4perl recommended for better logging
Reading '/Users/paulgallagher/.cpan/Metadata'
  Database was generated on Wed, 27 May 2026 07:54:20 GMT
Test::Warn is up to date (0.37).

The script warnings.t is a simple demonstration of how to assert that certain warnings are raised in tests.

use Test::More tests => 4;
use Test::Warn;

sub add_positives
{
    my ( $l, $r ) = @_;
    warn "first argument ($l) was negative"  if $l < 0;
    warn "second argument ($r) was negative" if $r < 0;
    return $l + $r;
}

warning_like { is( add_positives( 8, -3 ), 5 ) } qr/negative/;

warnings_like { is( add_positives( -8, -3 ), -11 ) }
    [ qr/first.*negative/, qr/second.*negative/ ];

When executed:

$ prove warnings.t
warnings.t .. ok
All tests successful.
Files=1, Tests=4,  0 wallclock secs ( 0.00 usr  0.01 sys +  0.01 cusr  0.00 csys =  0.02 CPU)
Result: PASS

Testing Exceptions

Test::Exception is a popular Perl module used to test if your code throws (or doesn’t throw) exceptions. It works alongside Test::More and provides specific functions to handle die() or other fatal errors during testing.

Exception::Class provides object-oriented exception handling, along with a convenient syntax for declaring hierarchies for them. It is a modern improvement to the old Error module that is no longer recommended.

Installation:

$ cpan Test::Exception Exception::Class
Loading internal logger. Log::Log4perl recommended for better logging
Reading '/Users/paulgallagher/.cpan/Metadata'
  Database was generated on Wed, 27 May 2026 07:54:20 GMT
Test::Exception is up to date (0.43).
Exception::Class is up to date (1.45).

The script exceptions.t is a simple demonstration of how to assert that certain exceptions are raised in tests.

use Test::More tests => 3;
use Test::Exception;
use Exception::Class (
    'MyException' => { description => 'My custom exception' }
);

sub add_positives
{
    my ($l, $r) = @_;
    throw MyException("first argument ($l) was negative")  if $l < 0;
    throw MyException("second argument ($r) was negative") if $r < 0;
    return $l + $r;
}

throws_ok { add_positives( -7,  6 ) } 'MyException';
throws_ok { add_positives(  3, -9 ) } 'MyException';
throws_ok { add_positives( -5, -1 ) } 'MyException';

When executed:

$ prove exceptions.t
exceptions.t .. ok
All tests successful.
Files=1, Tests=3,  0 wallclock secs ( 0.01 usr  0.00 sys +  0.01 cusr  0.00 csys =  0.02 CPU)
Result: PASS

Credits and References

About LCK#458
Perl

This page is a web-friendly rendering of my project notes shared in the LittleCodingKata GitHub repository.

Project Source on GitHub Return to the LittleCodingKata Catalog
About LittleCodingKata

LittleCodingKata is my collection of programming exercises, research and code toys broadly spanning things that relate to programming and software development (languages, frameworks and tools).

These range from the trivial to the complex and serious. Many are inspired by existing work and I'll note credits and references where applicable. The focus is quite scattered, as I variously work on things new and important in the moment, or go back to revisit things from the past.

This is primarily a personal collection for my own edification and learning, but anyone who stumbles by is welcome to borrow, steal or reference the work here. And if you spot errors or issues I'd really appreciate some feedback - create an issue, send me an email or even send a pull-request.

Follow the Blog follow projects and notes as they are published in your favourite feed reader