Let's start by understanding the concept of automated unit tests.
Let's say we have a very simple file called lib/MyCalc.pm
with the following content:
package MyCalc;
use strict;
use warnings;
use base 'Exporter';
our @EXPORT = qw(sum);
sub sum {
return $_[0] + $_[1];
}
1;
How can we test if the sum
function works properly?
First test script
We can start by writing a script that will call the function, and pass certain input to it. We can print the return values and observe them.
So we create a file called test.pl
with the following content:
use strict;
use warnings;
use FindBin qw($Bin);
use lib "$Bin/lib";
use MyCalc;
print sum(1, 1), "\n";
print sum(2, 2), "\n";
print sum(2, 2, 2), "\n";
The directory layout looks like this:
root/
lib/MyCalc.pm
test.pl
The $Bin
variable imported from the FindBin module always contains the
directory of the currently executing perl script. use lib "$Bin/lib";
ensures that the next line, use MyCalc;
will find the MyCalc.pm file in the lib
subdirectory.
Then we have 3 examples calling the sum
function and printing the result.
We can run our test script by typing in perl test.pl
and the output is:
2
4
4
It does not take a lot of thinking to understand that the result of the 3rd call is incorrect, but it is very easy to actually overlook it. That's how our brain works. Even in such a small example, the majority of people will not notice the problem. If this was some more complex task, some elaborate business logic, then we can't expect our programmers or testers to actually know the expected results. We would need to bring in a domain expert (or a manager) to provide us with input values and expected output values.
To make it easier for them we can change our code to also print the expected value next to the actual value:
Print expected value
use strict;
use warnings;
use FindBin qw($Bin);
use lib "$Bin/lib";
use MyCalc;
print sum(1, 1), " 2\n";
print sum(2, 2), " 4\n";
print sum(2, 2, 2), " 6\n";
The output now looks like this:
2 2
4 4
4 6
Let the computer compare
This is an improvement as now we don't need the expert any more, but it still needs a lot of attention to manually compare
the actual result with the expected result. Let's change our tests script to do that for us, and instead of the values,
print only ok
on success and not ok
on failure.
use strict;
use warnings;
use FindBin qw($Bin);
use lib "$Bin/lib";
use MyCalc;
if (sum(1, 1) == 2) {
print "ok\n";
} else {
print "not ok\n";
}
if (sum(2, 2) == 4) {
print "ok\n";
} else {
print "not ok\n";
}
if (sum(2, 2, 2) == 6) {
print "ok\n";
} else {
print "not ok\n";
}
And the output:
ok
ok
not ok
Refactoring and creating the ok() function
You might have noticed we had a lot of repetition in the above code. We could do much better.
Let's create a function called ok()
that will receive a
True or False value and will print ok
or not ok
accordingly.
use strict;
use warnings;
use FindBin qw($Bin);
use lib "$Bin/lib";
use MyCalc;
ok(sum(1, 1) == 2);
ok(sum(2, 2) == 4);
ok(sum(2, 2, 2) == 6);
sub ok {
my ($true) = @_;
if ($true) {
print "ok\n";
} else {
print "not ok\n";
}
}
The output is still the same:
ok
ok
not ok
use Test::Simple
All that is fine, and this ok()
really doesn't to much, but other people have already implemented it
and their ok()
function is actually more powerful and it is part of a huge ecosystem for testing.
So let's replace our home-made ok()
function by the one supplied by Test::Simple.
use strict;
use warnings;
use FindBin qw($Bin);
use lib "$Bin/lib";
use MyCalc;
use Test::Simple tests => 3;
ok(sum(1, 1) == 2);
ok(sum(2, 2) == 4);
ok(sum(2, 2, 2) == 6);
The change was simple, and the output of running perl test.pl
is a bit more
verbose than what we had earlier. It looks like this:
1..3
ok 1
ok 2
not ok 3
# Failed test at test.pl line 12.
# Looks like you failed 1 test of 3.
Test::Simple required us to state how many times we are going to call the ok()
function,
that's the tests => 3
in the use-statement.
In return it provided us with a counter and at the end it even reported that one out of three tests have failed.
We'll look at these features later on.