Unit Testing Perl Applications with Test::More
Learn how to write unit tests for Perl applications using Test::More, generate TAP reports, measure code coverage, and integrate with Jenkins CI.
Introduction
Unit testing is an important part of maintaining long-lived Perl applications.
In this article, we will walk through:
- Installing and using
Test::More - Organizing test files
- Writing basic test cases
- Understanding common assertions
- Generating TAP reports
- Measuring code coverage
- Integrating tests with Jenkins CI
1. Install Test::More
Fedora
sudo dnf install perl-Test-Simple
RHEL
sudo yum install perl-Test-Simple
Ubuntu
sudo apt-get install libtest-simple-perl
2. Test File Conventions
Naming
Test files should:
- Start with
Test - End with
.t - Be stored in the
t/directory
Recommended naming convention:
Test<ModuleName>.t
Example:
TestVendorProduct.t
Indentation
Use:
- 4 spaces
- No tabs
Make sure your editor is configured correctly before writing tests.
3. Create Your First Test File
#!/usr/bin/env perl
use strict;
use warnings;
use Test::More tests => 2;
my $got = 'Hello World';
my $expected = 'Hello World';
is( $got, $expected, 'Say Hello to World' );
my $got2 = 'Hello Moon';
my $expected2 = 'Hello Mars';
is( $got2, $expected2, 'Say Hello to Mars' );
Output:
1..2
ok 1 - Say Hello to World
not ok 2 - Say Hello to Mars
# Failed test 'Say Hello to Mars'
# at /home/kyan/perl-unittest/t/TestUnitCase.t line 12.
# got: 'Hello Moon'
# expected: 'Hello Mars'
# Looks like you failed 1 test of 2.
4. Understanding Test Plans
When writing a test file, you usually know how many tests will be executed.
There are two common ways to define the number of tests.
Static Test Plan
use Test::More tests => 2;
Using done_testing
use Test::More;
# Testing code...
done_testing(2);
If you do not know how many tests will run in advance, you can simply use:
done_testing();
5. Common Assertions
Basic Assertions
ok
ok($got eq $expected, $test_name);
is
is($got, $expected, $test_name);
isnt
isnt($got, $expected, $test_name);
Regular Expression Assertions
like
like($got, qr/expected/, $test_name);
unlike
unlike($got, qr/expected/, $test_name);
Comparison Assertions
cmp_ok
cmp_ok($got, $op, $expected, $test_name);
# Equivalent to:
# ok($got eq $expected);
cmp_ok($got, 'eq', $expected, 'this eq that');
Object and Module Assertions
can_ok
can_ok($module, @methods);
can_ok($object, @methods);
isa_ok
isa_ok($object, $class, $object_name);
isa_ok($subclass, $class, $object_name);
isa_ok($ref, $type, $ref_name);
new_ok
my $obj = new_ok($class);
my $obj = new_ok($class => \@args);
my $obj = new_ok($class => \@args, $object_name);
require_ok
require_ok($module);
require_ok($file);
use_ok
BEGIN { use_ok($module); }
BEGIN { use_ok($module, @imports); }
Deep Comparison Assertions
is_deeply
is_deeply($got, $expected, $test_name);
eq_array
my $is_eq = eq_array(\@got, \@expected);
eq_hash
my $is_eq = eq_hash(\%got, \%expected);
eq_set
my $is_eq = eq_set(\@got, \@expected);
Miscellaneous Assertions
pass
pass($test_name);
fail
fail($test_name);
subtest
use Test::More tests => 1;
subtest 'An example subtest' => sub {
plan tests => 2;
pass('This is a subtest');
pass('So is this');
};
6. Diagnostics
diag
diag(@diagnostic_message);
note
note(@diagnostic_message);
explain
my @dump = explain @diagnostic_message;
7. Special Testing Blocks
SKIP Block
SKIP: {
skip $why, $how_many if $condition;
# Normal testing code goes here
}
TODO Block
TODO: {
local $TODO = $why if $condition;
# Normal testing code goes here
}
8. Continuous Integration with Jenkins
Application Environment Configuration
Before running tests in Jenkins, your application environment must be configured correctly.
In many cases, configuration files differ between environments such as:
- Development
- Staging
- Production
A useful Jenkins plugin for managing configuration files is:
After installing the plugin:
- Go to Manage Jenkins → Managed files
- Add a new configuration file
- Configure your Jenkins job
- Enable Provide Configuration files
- Define the target location where the file should be placed
9. Generate TAP Test Reports
Install TAP::Harness::Archive
Fedora
sudo dnf install perl-TAP-Harness-Archive
RHEL
sudo yum install perl-TAP-Harness-Archive
Ubuntu
sudo apt-get install libtap-harness-archive-perl
Generate Test Reports
Create an output directory and run prove:
mkdir -p output
prove -r t/ --archive output
You can find the generated test results in the output/ directory.
Publish TAP Reports in Jenkins
Install the Jenkins TAP plugin:
Configure your Jenkins job:
- Add a post-build action
- Select Publish TAP Results
- Use the following test file pattern:
output/**/*.t
10. Generate Code Coverage Reports
Install Devel::Cover
Fedora
sudo dnf install perl-Devel-Cover
RHEL
sudo yum install perl-Devel-Cover
Ubuntu
sudo apt-get install libdevel-cover-perl
Install Devel::Cover::Report::Clover
This package is not commonly available through system package managers.
Install it from CPAN:
cpan install Devel::Cover::Report::Clover
Modern alternative:
cpanm Devel::Cover::Report::Clover
Generate Coverage Reports
cover --report clover
Publish Coverage Reports in Jenkins
Install the following Jenkins plugins:
Configure your Jenkins job:
Publish Clover Coverage Report
- Clover report directory:
cover_db - Clover report file name:
clover.xml
Publish HTML Reports
- HTML directory to archive:
cover_db - Index page:
coverage.html - Report title:
Coverage Reports
Example Jenkins Build Script
prove -r t/ --archive output
cover --report clover
Known Issues
Clover Plugin 404 Issue
Some versions of the Jenkins Clover Plugin may generate a broken Coverage Report link that returns a 404 error.
As a workaround, you can publish the generated HTML report using the HTML Publisher Plugin instead.