This old site is no longer updated. Visit the new site.

The Ruby vs. PHP BDD Beauty Contest: no contest

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 describe Environment, "created from the command line" do
 2   before(:each) do
 3     @env = Environment.new
 4   end
 5   
 6   it "should set the usage message attribute" do
 7     args = %w(foo bar baz quux)
 8     @env.parse_command_line(args)
 9     @env.usage_message.should_not be_empty
10   end
11   
12   it "should recognize arguments that are not options" do
13     args = %w(foo bar baz quux)
14     @env.parse_command_line(args)
15     @env.should have(4).args
16   end
17   
18   it "should throw an exception if an illegal option is provided" do
19     args = %w(foo -z bar)
20     lambda { @env.parse_command_line(args) }.should raise_error
21   end
22   
23   it "should allow the cvs path to be set" do
24     test_path = '/bin/cvs'
25     args = ['--cvspath', test_path]
26     @env.parse_command_line(args)
27     @env.get_option(:cvs_command_path).should == test_path
28   end
29   
30   it "should be invalid if cvs path does not exist" do
31     args = ['--cvspath', 'nonexistent']
32     @env.parse_command_line(args)
33     @env.should_not be_valid
34     @env.errors.should have_at_least(1).error
35     @env.errors_on(:cvs_path).should have(1).error
36     @env.errors_on(:cvs_path).should match(/not exist/)
37   end
38 end

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:

assert(wallet.dollars >= 2e6)

when you can read this:

wallet.should have_at_least(2e6).dollars

or this:

bank = mock('a bank with 2 million dollars')
bank.should_receive(:withdraw).at_least(:twice).with(1e6).and_return(:ok)

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:

<?php
$observer = $this->getStub('Observer', array('update'));
$observer->shouldReceive('update')->once()->with('something');

Meanwhile, at least one person, Pádraic Brady, is working on a BDD framework for PHP: the PHPSpec project. Unfortunately, the results are… less nice:

 1 <?php
 2 class DescribeNewBowlingGame extends PHPSpec_Context {
 3   private $_bowling = null;     
 4 
 5   public function before() {        
 6     $this->_bowling = new Bowling;    
 7   }    
 8 
 9   public function itShouldScore0ForGutterGame() {   
10     for ($i=1; $i<=20; $i++) {
11       $this->_bowling->hit(0);
12     } 
13     $this->spec($this->_bowling->score)->should->equal(0);
14   } 
15 }

Compare this to the RSpec code which inspired it:

describe Bowling do
  before(:each) do
    @bowling = Bowling.new
  end

  it "should score 0 for gutter game" do
    20.times { @bowling.hit(0) }
    @bowling.score.should == 0
  end
end

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:

What’s :this?

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 it as $this->_bowling.

Little things matter

Ruby uses object.method syntax. PHP uses $object->method. They look so comparable! That is, until you run into a chain like:

$this->spec(foo)->should->equal

which is less readable than:

this.spec(foo).should.equal

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 Object, and Objects can be monkey-patched. So you can magically attach new methods like should and should_not to the Object class and then write things like:

@bowling.score.should be_big

Here we’re calling the should method on @bowling.score, which is probably an Integer, although it might be a BowlingScore object, or a Real, or even Imaginary if we’re bowling on the planet Vulcan. Ruby does not care. Ruby can roll with those punches. Ruby rocks.

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->spec($this->_bowling->score)->should->equal(0);

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 about.

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 iterators…)

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 to 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->_bowling->score)->thing->should->equal(0)

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.