Spot the Test::Class Bug!
If you run prove on this code, it will fail. Why? Click "Continue Reading" for the answer.
package Check;
use Test::Class::Most parent => 'Test::Class';
use Readonly;
Readonly my $CONSTANT => 4;
INIT { Test::Class->runtests }
sub checkit : Tests(1) {
is $CONSTANT, 4, 'Constant should be set';
}
1;
OK, this is not really a Test::Class bug. The above code fails because of the INIT block. It will run before the Readonly assignment executes. One way to fix that is with the very clumsy:
package Check;
use Test::Class::Most parent => 'Test::Class';
use Readonly;
my $CONSTANT;
BEGIN { Readonly $CONSTANT => 4; }
INIT { Test::Class->runtests }
sub checkit : Tests(1) {
is $CONSTANT, 4, 'Constant should be set';
}
1;
This is a common anti-pattern in Test::Class tests. The reason many people don't see that is because they often have a single driver script per class:
use strict;
use warnings;
use Some::Test::Class;
That works because when you "use" a module, the code in that module will be executed and then the INIT block fires, but when you try to execute that module directly, the INIT block fires before the code is run. The semantics are tricky, but they work well once you understand them.
To get around this, I've just uploaded Readonly::BeginLift to the CPAN. Now you can do this:
package Check;
use Test::Class::Most parent => 'Test::Class';
use Readonly::BeginLift;
Readonly my $CONSTANT => 4;
INIT { Test::Class->runtests }
sub checkit : Tests(1) {
is $CONSTANT, 4, 'Constant should be set';
}
1;
And everything works just fine
Or, you could simply add this to the bottom:
Problem solved.
@pdcawley: sure, but I don't want to add that for two reasons.
First, I have to add that to the bottom of every Test::Class (it doesn't work if you put it in your base class). I'm trying to reduce boilerplate, not encourage it :)
Second, use my first code snippet and then add your line at the bottom. You'll find that if you do want a separate test driver for each class (as some prefer) then it still fails unless you do this:
Again, more boilerplate. just put this in your base test class and the boilerplate is minimized: