Showing posts with label perl. Show all posts
Showing posts with label perl. Show all posts

2018-05-04

Perl stubbing (mocking) a non-method subroutine

This is about Perl5 unit testing.

In Unit testing, you often want to mock some method of somebody else's code, i.e. you replace the method call with one that executes your own code, more appropriate to the test at hand.

This technique is called "mocking" if the faked subroutine is a class method. In the seemingly simpler case, where there are no classes and methods, only packages and subroutines, authors prefer the term "stub" to "mock", even though the idea is exactly the same.

However, creating a subroutine stub in Perl5 seems to be exceedingly difficult. All available documentation and CPAN modules refer to OO classes. Those modules that also mention stubbing (Mock::Modules, Test::Speck::Mock, and maybe others) do so only vaguely and provide no examples.

Here I present a generic solution to the problem. I create a package PackageA exporting a subroutine subA that simply prints "The Original" to stdout. The PackageB contains a subB which calls subA from PackageA, i.e. it also prints "The Original", but that's what we want to change. In a third package, called MyTest, we first call subB, then we replace the global reference of subA with our own subroutine that prints "The Stub". Finally we call subB again, but now in the stubbed version. Here is the complete example in one file:

package PackageA;
sub subA {
    print "The Original\n";
}

package PackageB;
sub subB {
    PackageA::subA();
}

package MyTest;
PackageB::subB();
no warnings;
local *PackageA::subA = sub {
    print "The Stub\n";
};
use warnings;
PackageB::subB();

This prints:

The Original
The Stub

Good! This is exactly what we want. - Sadly it does not work if we naively split the above code across three files using the well-known Exporter.

File PackageA.pm:
package PackageA;
use Exporter;
@ISA = qw(Exporter);
@EXPORT = qw(subA);
sub subA {
    print "The Original\n";
}
1;

File PackageB.pm:
package PackageB;
use PackageA qw(subA);
sub subB {
    subA();
}
1;

File MyTest.t (does not work):
use PackageA qw(subA);
use PackageB;
PackageB::subB();
no warnings;
local *PackageA::subA = sub {
    print "The Stub\n";
};
use warnings;
PackageB::subB();

Executing perl MyTest.t prints:
The Original
The Original

No stubbing takes place, the original Package::subA is executed in both cases! How comes? - The reason is the way the Exporter/use modifies the global symbol table. Importing a subroutine does not simply create a new subroutine that calls the original one (this would be an unnecessary redirection). Importing a foreign symbol copies its reference into the own namespace. All this happens before any statements are executed, so at the time of printing, the entry *PackageA::subA still points to the original subroutine, while there is now a new entry *PackageB::subA, created by "use PackageA", that also points to the original subA, and that's the one we want to replace:

local *PackageB::subA = sub {

With this modification, the stub is actually executed: perl MyTest.t prints

The Original
The Stub

The correct MyTest.t:
use PackageA qw(subA);
use PackageB;
PackageB::subB();
no warnings;
local *PackageB::subA = sub {
    print "The Stub\n";
};
use warnings;
PackageB::subB();

I shall be happy to use a CPAN module that does the above, as soon as somebody points me to one. Until then, my unit tests will use the technique outlined here.

2009-11-11

Unix time conversion

Good evening,

This is my first post in my first blog. After a 29 years in the computer business, I finally decided that blogs may be a good thing, after all. Not for reading, no, but I finally found it may be a good thing to write. So here comes my first snippet, a discovery that eluded me since the first of January, 1970. Or nearly so: the Unix time format and its conversion to and from the human calendar.

Question:
How do I convert a Unix timestamp into a human-readable date-time string?

Answer:

date -d @<timestamp>

Example:
$ date -d @1257958548
Wed Nov 11 17:55:48 CET 2009

Explanation:
The GNU version of the date command takes the -d option. An @-sign in front of a plain number specifies a Unix timestamp (i.e. the number of seconds since 1970-01-01). This works only on GNU/Linux date, all other Unix variants are helpless.


Question:
How do I convert human-readable date-time string into a Unix timestamp?

Answer:

date -d "Month Day hh:mm:ss TZ Year" +%s

Example:
$ date -d "Nov 11 17:55:48 CET 2009" +%s
1257958548

Again, you must use date from GNU/Linux or you are out of luck.

Enjoy!