Considering what to test with phpspec

Shane Auckland
shanethehat
Published in
3 min readNov 8, 2013

--

I have recently started using phpspec in place of PHPUnit as a PHP development tool. So far for me, the transition from TDD to specBDD is a fairly trivial one. The vocabulary used in the specs is slightly different to xUnit tools, and this seems to make me think more carefully about what I really want to test, or better, what behaviour I want my object to exhibit.

That’s the key for me; I’m not testing the object, I’m declaring its expected behaviour. BDD, not TDD.

The language that is used in specs is all future tense. You’re not performing an operation and testing the result. Instead, you declare what the object should do. The use of “should” in this way implies that the object doesn’t do it yet, but at some point in the future it will.

It’s not so much a change of approach as a change of mindset. The familiar cycle of red, green, refactor is just the same as it was. What’s different is how your thought process is guided. I find that there is no consideration for testing implementation details, an anti-pattern that xUnit tools make it possible to fall into. All the spec lets you care about is what the object should appear to do to an outside observer. What you get from the object’s interface. It’s that simple.

An example

Here’s an example of something that happened to me yesterday. I have an object that implements the Countable interface, and accepts an array into its constructor. The count() method simply returns the length of the array. To test that this works as expected using PHPUnit, I would possibly have written the following test and moved on:

public function testObjectCanBeCounted()
{
$obj = new MyObject(array('item1', 'item2'));
$this->assertCount(2, $obj);
}

This test would work. I might have wanted to test other conditions, like an empty array, but fundamentally this does what is required. Under the hood, PHPUnit checks that $obj implements Countable, and then calls the count($obj) and compares the result to my assertion.

But wait…

I tried to recreate this test with phpspec, and found I didn’t know how. So I reached out to the phpspec community at Inviqa to ask whether it was enough to check that the Countable interface was properly implemented, and got the following reply:

the MyObject::count() method is part of your public interface and that’s what you should spec

This made me think. As a PHP developer, I know what the expectations of the built in count() function are. I know that my object must implement Countable, and provide a public count() method that returns something resembling an integer. So why am I tempted to test a feature of the language? That’s something out of my control. What is in my control is that I implement the correct interface and return the correct value from my public interface. With that in mind, I wrote these specs:

function it_is_countable()
{
$this->shouldImplement('Countable');
}
function it_returns_the_expected_count()
{
$this->beConstructedWith(array('item1', 'item2'));
$this->count()->shouldReturn(2);
}

What's the difference, and does it matter?

The difference is that by using the built in count() function in your test you are inadvertently testing the functionality of a language feature. By only testing the requirements set down by that language feature as they apply to your object, you are only testing your object specification.

Whether this matters is something that will probably split opinion. On one hand, why should I be responsible for testing that the language works as it says? On the other, wouldn't it be beneficial to know when upgrading my PHP version in future, that the built in count() function no longer works as expected?

I'm still in two minds about this. while I like that my tests/specs are more focused, I'm concerned that I'm not doing everything I can to ensure future compatibility with a language feature that I use every day. Either way though, I am very happy with the way that phpspec has made me rethink my testing approach.

--

--