Considering what to test with phpspec

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.

5 Comments

  • 8th November, 2013 - 6:34 pm | Permalink

    I am not sure I understand your point about testing an inbuilt language feature, calling $this->count() in your spec is not using an inbuilt language feature, it is using the MyObject::count method that you have defined as part of the contract in implementing \Countable.

    You are defining behaviour, and therefore you need to write a test – xUnit or PhpSpec.

    Consider a very simple collection


    $collection = new Collection();

    $collection->add('item');
    $collection->add('another item');
    $collection->add('item');

    // What would you expect here? Probably 3...
    echo count($collection);

    // or
    echo $collection->count();

    This could be simply implemented like this


    items = $items;
    }

    public function add($item)
    {
    $this->items[] = $item;
    }

    public function count()
    {
    return count($this->items);
    }
    }

    But what if you wanted to only return a count of unique items? There a couple of ways this might be achieved, without an example we can never see that behaviour in a broken or validated state.


    add('item 1');
    $this->add('item 2');
    $this->add('item 3');

    $this->count()->shouldReturn(3);
    }

    // this test would fail
    function it_only_counts_unique_items()
    {
    $this->add('item');
    $this->add('item');
    $this->add('item');

    $this->count()->shouldReturn(1);
    }

    With that failing test we can pick an implementation (for my example either implementations, or both would work)


    class Collection implements \Countable
    {
    // ...

    public function add($item)
    {
    if (!in_array($item, $this->items)) {
    $this->items[] = $item;
    }
    }

    // alternatively use array_unique
    public function count()
    {
    return count(array_unique($this->items));
    }
    }

    • 8th November, 2013 - 9:14 pm | Permalink

      I am not sure I understand your point about testing an inbuilt language feature, calling $this->count() in your spec is not using an inbuilt language feature, it is using the MyObject::count method that you have defined as part of the contract in implementing \Countable.

      That was the point I was making really. In the PHPUnit example I tested using the built in count() function, but in the phpspec example, because there was no obvious way of doing that, I was forced to only testing the actual implementation of the object’s interface.

      You are also absolutely right. This was only a very simple example, and in a more complex collection you need specs to cover all of the describable behaviours of your object.

  • 8th November, 2013 - 6:43 pm | Permalink

    Code formatting failure… here is a gist instead https://gist.github.com/peterjmit/7375500

  • 8th November, 2013 - 10:43 pm | Permalink

    So are we talking about the fact that when using PHPunit, if you strip away the implementation detail of the assertion framework, doing

    $this->assertCount(2, $obj);

    basically results in

    if (count($obj) === 2) // ..

    whereas in PhpSpec when you use

    $this->count()->shouldReturn(2)

    this results in a check similar to

    if ($obj->count() === 2) // ...

    • 9th November, 2013 - 11:20 am | Permalink

      Yeah exactly. In the case of phpspec we are guided into testing the object implementation. Of course, in PHPUnit we could be more care about our test:

      $this->assertEquals(2, $obj->count());

      But the point I was making is that phpspec naturally guides us to this approach.

  • Leave a Reply

    Your email address will not be published. Required fields are marked *

    You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>