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:
describe Environment, "created from the command line" do
before(:each) do
@env = Environment.new
end
it "should set the usage message attribute" do
args = %w(foo bar baz quux)
@env.parse_command_line(args)
@env.usage_message.should_not be_empty
end
it "should recognize arguments that are not options" do
args = %w(foo bar baz quux)
@env.parse_command_line(args)
@env.should have(4).args
end
it "should throw an exception if an illegal option is provided" do
args = %w(foo -z bar)
lambda { @env.parse_command_line(args) }.should raise_error
end
it "should allow the cvs path to be set" do
test_path = '/bin/cvs'
args = ['--cvspath', test_path]
@env.parse_command_line(args)
@env.get_option(:cvs_command_path).should == test_path
end
it "should be invalid if cvs path does not exist" do
args = ['--cvspath', 'nonexistent']
@env.parse_command_line(args)
@env.should_not be_valid
@env.errors.should have_at_least(1).error
@env.errors_on(:cvs_path).should have(1).error
@env.errors_on(:cvs_path).should match(/not exist/)
end
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:
$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:
class DescribeNewBowlingGame extends PHPSpec_Context {
private $_bowling = null;
public function before() {
$this->_bowling = new Bowling;
}
public function itShouldScore0ForGutterGame() {
for ($i=1; $i<=20; $i++) {
$this->_bowling->hit(0);
}
$this->spec($this->_bowling->score)->should->equal(0);
}
}
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.
Comments
The freakish thing writing a BDD framework for PHP is definitely the syntax. That said I'd venture it does still have value - maybe not as much as RSpec (which I use regularly for Ruby) but enough that it can spark some improvement in other areas of PHP. I think a lot of this is simple to explain - PHP doesn't have a huge population of testing/TDD/BDD tools to start with. Any hamfisted effort is an improvement :).
As you noted SimpleTest has excellent Mock Object support, and has done for years. With PHPSpec now requiring the introduction of a PHPMock library for it's own use (since the SimpleTest one is internal) it's also been adopted by PHPUnit 4. Then there's PHPMutagen - another first for PHP.
Personally I think PHPSpec will drive progression outside it's BDD origins - at least until I figure out how to minimise PHP's inexhaustible efforts to defeat a nice looking API...sigh.
I'd love a book which surveyed the history of language features in Ruby inspired or copied from languages as LISP, Smaltalk, Perl, etc. Like going back to the roots and discuss why they was chosen and why it's implemented in a particular way in Ruby. Are we missing certain things these languages have to offer in Ruby? How does these choices relate to code beauty and programmer happiness?
I'd love to see a book on Capistrano 2! Especially how it can fit into existing infrastructure (php/mysql/subversion) while allowing new projects to be rails/camping/nitro/sinatra based.
Have a list of things you need from the infrastructure/server admin team as a minimum, and list things that would be nice to have.
Also stats on avg requests per second etc. for rails would be great to show a boss! Maybe even with some examples of how businesses have used rails and the benefits of it over existing frameworks.A book that talks about the Ruby internals. How the language is implemented and what defines Ruby as a language. With all the work being done implementing ruby on top of various VM's (JVM, Rubinius, YARV, .NET CLR), a book that explains how one goes about doing something like would be very very helpful.
Yes, PHP syntax is bad. $this-> is something that should never happen to any programming language.
As a long-time c# and Java developer and an occasional php developer, making the switch to ruby has been eye-opening. I couldn't agree more with your comments about the ugly nature of php syntax. I recently worked on a project where by the 3rd month I had non-tech people reading source code along with me in ruby. They understood it because it was like reading plain english requirements. Imagine that conversation in php ever happening (or even in c# or java most of the time). When you add in the discussion with them that we just wrote a test case and they look at you and say 'I thought we wrote a requirements document' and you say 'yes, well, we did that too. they are the same thing'. Then you wake up out of the permafrost of php and realize that rspec and cucumber change the rules of the traditional walled-off tech vs business game in most companies and realize that the two can speak the same language.
Fighting PHP syntax is hard. But it's not an impossible task. For example, I have developed a featured-light BDD framework. For instance:
$this->object($some_object) ->isInstanceOf('STDClass') ->doesNotHaveProperty('non_existant_property');Can you compare that with Cucumber?? That is not BDD. At all.
PHP-Code is so ugly!
Post new comment