For the last week I’ve been writing a Drupal utility in Ruby. This is a dubious decision, because most Drupal developers would prefer a tool written in Drupal’s native PHP. It’s less hassle to install, and less hassle to modify.
But I went with Ruby anyway, at least for version 0.1 – partly to keep myself in practice, and partly as an excuse to work out with RSpec, the Behavior Driven Development (BDD) framework for Ruby.
With BDD, before you write your code, you write a spec. Here’s a section of one of my specs – it describes an Environment object that can parse a Unix command line and pick out the arguments and the option flags:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
The idea is that you write a sentence about what the code should do, along with an executable description that demonstrates the sentence. Then you write the code itself until the description runs without failing.
Advocates of Test-Driven Development (TDD) will recognize this as nearly the same thing. But it does have a few advantages:
BDD avoids using the word “test”, which carries uncomfortable connotations like “broken” and “buggy” and “ISO 9002” and “let’s do that last, after the documentation”. BDD uses words like “describe” and “should” to guide you into a proper, positive frame of mind.
BDD in Ruby uses nifty readable syntax. The syntax is astoundingly addictive. Why read this:
when you can read this:
Oh, sweet RSpec mock syntax. How I wish you had an equivalent in other important languages, like PHP!
Well, the good news is that PHP has testing advocates of its own. Mock objects are apparently well covered by Simpletest, which just happens to be Drupal’s preferred PHP testing library. And the syntax looks familiar – it’s quite nice, despite some additional clutter:
1 2 3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Compare this to the RSpec code which inspired it:
1 2 3 4 5 6 7 8 9 10
It’s enough to make you cry.
The thing is, I’m completely convinced that Pádraic is doing the best he can. I am no expert on PHP syntax, having learned it by osmosis from Drupal code, and maybe after I finish Programming PHP I’ll know better, but PHPSpec seems to be fundamentally stymied by the ugly syntax of PHP. Specifically:
Why, it’s PHP’s inability to understand object scoping! You would
think that declaring
_bowling to be
private to a class would be
enough of a clue, but it looks like you’re still obligated to refer to
Little things matter
object.method syntax. PHP uses
look so comparable! That is, until you run into a chain like:
which is less readable than:
It’s all about the typography: the whitespace above the tiny dot separates the words better and makes their shape easier to recognize.
It’s good to be an Object
In Ruby, everything is an object, every object is a child of
Objects can be monkey-patched. So you can magically
attach new methods like
should_not to the
class and then write things like:
Here we’re calling the
should method on
@bowling.score, which is
Integer, although it might be a
Real, or even
Imaginary if we’re bowling on the planet
Vulcan. Ruby does not care. Ruby can roll with those punches. Ruby
Alas, PHP prosaically insists that you call the
should method on
specific objects that can actually understand it. So we have to wrap
the subject of
should in a
$this->spec(), like this:
This line makes my brain hurt because it’s got two instances of
$this, both of which are 100% free of significant meaning. The thing
which they refer to – the enclosing
PHPSpec_Context object – is an
irrelevant piece of scaffolding that I do not want to have to think
The PHP version of the Bowling spec reads like this:
This paragraph is a spec, and it-describes-Bowling. Consider the game of bowling that we will discuss in this paragraph. The score-should-be-zero-for-a-gutter-game. That is, if a ‘hit’ that scores zero pins happens twenty times, this sentence of this paragraph will be true: this paragraph’s game’s score should equal zero.
Here’s the RSpec version:
Let’s talk about Bowling. Consider a game of bowling. The score should be zero for a gutter game. That is, if a ‘hit’ that scores zero pins happens twenty times, the game’s score should equal zero.
(Note: Fun as it would be to write down the prose equivalent of a
for loop – it would look like a number-theory textbook – I decided
not to blame PHP for that. I’m pretty sure the language does have
PHP’s syntactic cyanide is so bitter that I wonder: is PHPSpec worth it? If the language itself insists on mangling my syntax, why should I bother trying to apply the subtle shading that is BDD? It just gets lost in the noise. Here’s a comparison from the PHPSpec docs themselves: the TDD version (PHPUnit):
$logger = new Logger; $this->assertTrue($logger->hasFile());
and the BDD version (PHPSpec):
$logger = new Logger; $this->spec($logger)->should->haveFile();
Frankly, I think the first version is clearer: it’s shorter, the call
hasFile() is in its natural location, and since neither version
is readable, why not pick the one that’s closest to idiomatic PHP?
Others seem to agree with me; just look at
this comment thread.
Pádraic has his work cut out for him, battling heroically against the fundamental structures of the PHP universe. I really feel for the guy, and would sincerely like to help. My personal inclination is to build a macro processor that allows you to write something sensible and have it JIT-compiled into PHP. But maybe I can get used to PHPSpec if I make some subtle vocabulary changes:
This thing, this PHP syntax thing… we will learn to cope with it.
Update: Jan 6, 2012
Dan Bernardic writes:
“All PHPspec is missing is a DSL on top of what he has. Except that I expect it would be a better idea to implement a way for ruby to run PHP code and inspect PHP memory state, just cause rspec for ruby already exists…”
and includes a proof-of-concept Gist.
I’ve toyed with this idea myself - which is to say, I’ve sat in an armchair and imagined it, not actually hacked on it - but there are drawbacks. The biggest is that it’s not enough to test one’s PHP code in isolation; one should test it in an environment that’s as close as possible to the production environment. Unless you plan to run your production PHP code inside a Ruby wrapper - and, believe me, you don’t - a Ruby-with-embedded-PHP testing framework is going to trade reliability for syntax, and that’s not a good trade unless the syntax is truly brainf**ked.