Test::Class::Sugar 0.3, no, 0.4
tap tap… Is this thing on?
So, I recently noticed that Test::Class 0.33 got released, which means that Test::Class::Sugar no longer needs to depend on a development release, and I also noticed that it was embarrassingly easy to throw Test::Class::Sugar into an infinite loop by forgetting which way the >> goes when you want to specify the number of subtests in a test method.
So, I’ve done a quick fix of the infinite loop problem as well and uploaded version 0.3 to PAUSE, so now you can write your tests like:
testclass exercises ClassUnderTest {
test creation of the class under test {
lives_and {
isa_ok ClassUnderTest->new, $test->subject;
}
}
...
}without having to jump through the hoops of downloading a development version of Test::Class or worry about accidental infinite loops…
Next up, fix the syntax to either allow both << and >> as test count specifiers, or come up with a more memorable way of separating the count from the test name.
Update:
Shortly after I released 0.3, Joel Bernstein asked if I’d be interested in a topic branch to make Test::Class::Sugar work with perl 5.8.
“Of course!” I said.
One day later, there it was. Thank you to Joel and to his employers, NET-A-PORTER for sponsoring his work. So now, Test::Class::Sugar 0.4 is winging its way to CPAN and now I have no excuse for not using it at work.
Test::Class::Sugar released 2
I’ve just pushed the second version of Test::Class::Sugar (first discussed here). It’s pretty much as discussed in the original article, but after some discussion with David Wheeler, I’ve dropped the +uses clause from the testclass declaration in favour of less DWIMmy (and thus easier to explain behaviour).
I’ve also introduced a defaults argument at use time. The only working default in this release is one which lets you specify the prefix used to derive the test class name from the class/module under test. I’ve documented a couple of extra and so far unimplemented defaults as well.
Have a play, you might like it.
Thinking about the virtues 3
I got a bit of stick on IRC last night for some of the choices I’d made when I was writing Test::Class::Sugar, in particular because one of the prerequisites is chromatic’s handy and opinionated Modern::Perl module. The ‘controversial’ aspect of Modern::Perl is that, when you use it, your code won’t run on any Perl before Perl 5, version 10.
The thing is, I don’t care about older perls any more. Version 10 features like ~~ and // are too convenient to fart about writing circumlocutions just to run on a version of Perl that I have no intention of ever using again.
Actually, that’s not quite true, those version 10 features are too convenient for me to fart about working around their absence. If you think that a module I write is useful enough that you want it to work on version 8, then of course I’ll accept your patch. But don’t be surprised if, when I start adding new features, I break the backward compatibility.
Also, on the happy day that version 10.1 escapes the pumpking patch, I’ll be setting that as my minimum perl version even if chromatic doesn’t bump the version number in Modern::Perl.
It’s all about the virtues
Your context is different from mine. I’m writing in Perl again for my own amusement more than anything. There are developments in modern Perl – tools like Moose and Devel::Declare – that I think are exciting and important. The Announcements project I started was as much about playing with the new tools as it was about trying to write something of wider utility. Test::Class::Sugar arose as a direct result of attempting to write Announcements and the desire to write test classes without hoopage. My principle drive then, is impatience to get Test::Class::Sugar to the point where I can get back to writing Announcements.
But then laziness and hubris kick in. So the code needs some polish. The parser and the code generator need to be disentangled, I need to get Adrian Howard to apply the little patch I had to make to Test::Class. Laziness demands I document it.
Impatience tells me to lean on the features of modern perl – that way I can get back to being a user of the new library as quickly as possible. Laziness tells me that I’m not going to need backwards compatibilty. Hubris tells me my work is good enough that someone who does will like it enough to send me a patch.
Everybody wins.
Magic vs Mundane: Keeping them apart
In which your correspondent does magical battle with the guts of Perl and emerges bloodied, but unbowed with a useful principle to code by.
Skip to the conclusion if you’re uncomfortable with the guts of the Perl runtime
Test::Class had me tearing my hair out earlier. There I was, happily transforming
test something {
ok 1;
};into something very like1:
*test_something =
Sub::Name::subname(
'test_something'
=> sub : Test { ok 1 }
);through the magic of Devel::Declare, but Test::Class didn’t seem to be playing fair. Instead of letting my tests run happily, it was complaining that it:
cannot test anonymous subs – you probably loaded a Test::Class too late (after the CHECK block was run). See ‘A NOTE ON LOADING TEST CLASSES’ in perldoc Test::Class for more details
The thing is, I wasn’t loading Test::Class too late. The problem is that, at the point I applied the Test attribute to my sub, the sub didn’t have a name and, because of the constraints you’re operating under when you’re using Devel::Declare to do code transformation, there was no obvious way to give it a name in time.
Incompatible magics
The trouble is, Test::Class does what it does through the magic of compile time code attributes, and, further, it relies on the fact that if a perl subroutine that gets inserted into the symbol table like this:
sub has_a_name {...}Then, when you get hold of a reference to that code by other means (say, in the subroutine that handles the setting of an attribute, that code ref knows its own name. However, if a subroutine that ends up in the symbol table like this:
*anonymous_ref = sub {...};Doesn’t know its name, unless you take advantage of the Sub::Name module.
So, in my generated code, I was giving my coderef a name, but it was happening to late. At the point that Test::Class::Test method was seeing the coderef, the coderef was anonymous.
My magic and Test::Class’s magic were incompatible.
The thing is, both sorts of magic are really just sugar for some pretty mundane donkey work. Test::Class does what it does through attributes because no flesh and blood programmer in their right mind would want to write something like this every time they wanted to write a test method:
sub test_something {
...
}
__PACKAGE__->mark_as_special_method('test_something', 'test', '3');In fact, mark_as_special_method doesn’t even exist as its own subroutine. The code that marks a method as special is just part of the body of the Test attribute handler.
Conclusion
Which brings me neatly to my conclusion.
When you’re designing a module that does anything magical, consider starting with a mundane core API that handles the business side of things. Then layer your magic on top of that API. Then document the API and the magic. Obviously the magic bits go up front in the docs, and the API goes in its own section (or even podfile) down at the bottom, where only eejits like me, who want their magic to work slightly different to yours, will bother reading it.
Obviously, I’m motivated by an issue I’m having with a particular module from CPAN, but the principle of separating the magic and the mundane is applicable everywhere. It’s called Separation of Concerns, or The Single Responsiblity Pattern. I call it a Just Story.
You’ll find the pattern in well designed websites that are using unobtrusive javascript to wave an AJAX wand over the site. You’ll see it woven through books like The Structure and Interpretation of Computer Programmers – where it’s called an Abstraction Barrier.
Patches sent
It turns out to be very easy to add mark_as_special_method (though I actually wrote it as ‘add_testinfo’ in the patch) to Test::Class. It’s about as straightforward an Extract Method refactoring as I’ve ever done – even without automated tools, I managed not to fuck it up. There’s a patch in Adrian Howard’s inbox, and I’m hopeful that it’ll be applied soon.
1 Not exactly – that’s the result of calling the shadowed test subroutine which was the result of the code transformation.
Check out the osfameron fork of Devel::Declare for the beginnings of some decent documentation which explains what’s going on.
