My top ten favourite PHPSPEC limitations
PhpSpec is enjoying a growth in popularity lately, probably related to the recent release of 2.0. Lots of people have been playing with it and trying to get to grips with what it can do.
Naturally they try to do the same things they would with other testing tools. Soon they find out they can't. 'Oh! This PhpSpec has so many limitations...I can't do this...I can't do that...'. Ironically, other people make positive comments about the same “limitations”. So I decided to publish a list of my top ten favourite limitations of PhpSpec, and why I love them so much.
Limitation #10: I can't test private methods
You can't assert against a private method with PhpSpec. You can only make assertions against public methods. OK, PHPUnit doesn’t offer you the ability to test private methods either, but PhpSpec makes it even clearer that a test case is a specification of the object behaviour, and test methods are examples of how it should be used.
In PhpSpec, when you use $this from your tests, you are making calls to the Subject Under Specification (SUS) – the object of the class you are testing. Let’s see an example in PHPUnit and compare it with PhpSpec.
In PHPUnit you would follow the traditional Arrange/Act/Assert pattern:
// arrange $this->calculator = new Calculator; // act $result = $this->calculator->add(1,1); // assert $this->assertSame(2, $result);
In PhpSpec however, a test method is viewed strictly as examples of the object behaviour, so you make the calls directly using $this, instead of needing to create an object.
$this->add(1,1)->shouldEqual(2);
The reason I love this limitation is that it forces me to think of the interface of my objects straight away. At first glance you may think that this is just a convenience feature, saving us having to instantiate the SUS, store the instance in the test case object, and then make a call and assertion.
But what this does is force the developer to think in terms of the object interface – the methods you can call – right from the start. The examples (test methods) are the object protocol: they describe how the object will be used, their public interface, or how the various methods of the interface will be used together to achieve the desired behaviour. Having your test focused on the interface rather than the internals of your class is what makes the test decoupled and more meaningful.
The fact that PhpSpec is smart enough to let you use $this in the context of the object being described can be a bit daunting for newcomers. If you are curious about the internals of PhpSpec you can read this interesting article by Peter Suhm.
Limitation #9: I can't assert against values coming from outside my object API
Here is a massive one: you can’t test against anything that is not coming from a value returned by one of the public methods. You heard me: nothing! Assertions in PhpSpec are called matchers, and they are only possible against either the public methods of the SUS, or the public methods of an object returned by those methods.
What this limitation is doing is forcing you to follow the law of Demeter. In addition, Prophecy, the underlying mocking framework, does not support stub chains, making it even more difficult to violate Demeter. If you're really desperate to break the law, then you will have to install the phpspec2-expect extension. This works in a similar way to the assertion in PHPUnit, but keeps all the syntax sugar of PhpSpec.
Here is an example of how it works:
expect(file(“app.log”))->toHaveCount(42);
Anything you put inside of the expect function becomes a subject to which you can apply all the existing PhpSpec matchers. Just make sure to use 'to' instead of the usual 'should': e.g. '…->shouldBe(…)” becomes “expect(…)->toBe(…)'. Negative matchers will work in the same way.
Of course, there is a good reason why this is not in core, and I strongly recommend you take this limitation seriously.
Limitation #8: I can't do integration tests
If you find yourself needing to use the phpspec2-expect extension often, this, among other signs, indicate you are trying to test how components work in integration. The thing is, PhpSpec was not designed for integration tests. In fact, it wasn't designed for testing anything really. It was designed as a tool to help you come up with well-designed classes. So, you may be able to find extensions that enable some integration testing capability, but the vanilla PhpSpec won’t offer much help.
I find this really useful to keep myself focused on the responsibilities of the object I am describing. I remember how easy it was for me to test too much in one goal, and found it really hard to get back in control of the design. So, PhpSpec reminds me that I am describing my object and its behaviour, rather than making sure my application works as a whole. As soon as my tests are back in the green state again, I run my Behat suite for that.
Another lesson PhpSpec taught me was how to isolate my code from the code I don’t own, adding adapters in between to unit test the logic I was adding. I maintain that the need for integration tests indicates coupling. Nicely broken-down independent pieces of software can be nicely described in isolation. I have given an example of how to use such adapters on a previous post - scroll down to 'Using an adapter'.
Limitation #7: you can't have code coverage
PhpSpec core does not offer code coverage. PhpSpec is a tool inspired by other BDD tools like rspec. One of the things about BDD is that it has a didactical element. It really helps me understand what TDD is all about. More about design, communication, messages and focus and less about structure, verification or obsessive quality.
Code coverage cannot prove that all intended behaviour of your classes are covered with tests; it merely tells you what parts of your code have been hit by tests.
In PHPUnit there is a @covers annotation. This allow the developer to specify what methods are covered by the test. That way the code coverage report will display nicely in green the private methods source code displayed in the report.
// PHPUnit covers annotation example /** * @covers MyClass::thatParticularPrivateMethod * @test */ function it_tests_some_code_I_know_to_be_in_some_private_method() { // test something }
The reason this is a bad idea is that it gives knowledge to the tests about where exactly in the class the behaviour has been coded, leading to coupling between test and code, and I've previously voiced my concerns on this approach. Although code coverage is not present in the PhpSpec core, and we have no plans to add it, there is an extension written for that purpose, for those who are extending PhpSpec to achieve something beyond the initial proposed goal of the tool.
Limitation #6: I can't use a data provider
Data providers are functions written separately from the test, designed to allow the developer to run the same test but using pre-specified different fixtures at every run. However clever this may sound, it forces you to use the same test method (the code example) to describe various behaviours. This might be handy for those approaching the exercise from the verification or confidence angle, but the communication gets left behind a bit.
It is a lot harder to go over the data provider and see what you are trying to test at every row of data. It is also very common that, when I see myself thinking of data providers, I imagine the user providing the data, and expect that to come from my acceptance tests. I will often just quickly jump into Behat and create an example table for cases like this. These are called scenario outlines in Behat, and you can see examples of how to do them here.
Limitation #5: I can't mock abstract methods
When you first run PhpSpec to generate a new spec and you open it you will see that your spec class will extend `ObjectBehavior`. This sends a message that a spec is not a test case; it is a canvas for you to design objects, describing their behaviour.
For starters, abstract methods don’t have any behaviour, nor can abstract classes be instantiated as objects. Abstract classes are incomplete. Once you come to a implement concrete objects with the behaviour contained in abstract classes, you can then provide examples of how that will be used in those concrete implementations.
In some cases however, you have to provide examples of how to use a convenient abstract class, describing whatever concrete implementation that abstract class has. To do this, use the `beAnInstanceOf` method of PhpSpec's `ObjectBehavior`: You will have to provide (along with the spec class) a class that extends the abstract class (we usually keep this in the same file as the spec) and pass the name of that subclass to `beAnInstanceOf`.
namespace spec; use PhpSpec\ObjectBehavior; class SomeAbstractClassSpec extends ObjectBehavior { function let() { $this->beAnInstanceOf('spec\SomeConcreteSubClass'); } // examples... } class SomeConcreteSubClass extends SomeAbstractClass { }
Limitation #4: my tests can't follow a code standard
This is an interesting recurring complaint. When we first launched PhpSpec and announced that the syntax for the example titles (test method names) would use underscores instead of camel-case, we saw some resistance from a few developers in the community.
Even though we decided specs deserved a domain specific standard, we didn’t insist in presenting that as an argument so much as asking them to just try the new syntax. The response has been overwhelmingly positive. The same people who once raised the question about the new syntax, after trying it for themselves, were the first to defend it against new criticism as it arose.
Of course you can follow a code standard, and you should:For your code, follow code standards. For your specs... follow spec standards.
Limitation #3: I can't make assertions about state
PHPUnit has the convenient `readAttribute` that allows you to capture the value of a private property. You can then use that in an assertion to check that the object is in the state you want it to be after you've done something with it.
// PHPUnit readAttribute /** @test */ function it_changes_the_state() { $sut = new SUT; // change SUT state // ... $this->assertSame('expected state', $this->readAttribute($sut, 'somePrivateProperty')); }
I should be able to describe all the behaviours of my object by messaging alone. Telling my objects to do stuff rather than describing its internal functioning from my test has the added advantage of decoupling the tests from the class implementation. After using PhpSpec, I often find myself changing internally how my object operates to achieve the same behaviour without having to change a line in the test.
Also, I struggle to see how changing the state of the object is a behaviour at all. As programmers, all we do is to get the information to the right place in the most effective way. We change the state of the object to do 'something' with that in memory state. Changing the state is very rarely the final behaviour I envision for that object.
Not being able to test state helped me write better tests, focused on what my objects do, rather than how they do it.
Limitation #2: I can't mock methods of the sus
This limitation can become very visible in the case of using inheritance to extend behaviour. We inherit a class and want to add a new method that internally delegates some behaviour to a parent class method. We cannot mock or stub that method. This has lead me again and again to favour composition over inheritance, which is a golden principle in OO design. In the few cases in which inheritance is justified, we can isolate the reusable behaviour into a separate object and use composition in the parent object, allowing us to replace the collaborator with a double.
Limitation #1: I can't test my legacy code
Hopefully I have already established that PhpSpec is not a great tool for testing anyway. It was not even designed to fulfil that goal. It was meant to push your Test Driven Design – a development tool to enable that emergent design.
Going back and writing tests for existing code wasn’t in our list of use cases. PhpSpec is great to inspire clear dependency management between objects, and if what you have is a bunch of legacy objects that is already filled with design smells like coupling, complexity and opacity, then PhpSpec, which makes life difficult to add those smells in the first place, will not be much of a friend.
Nevertheless, I do inherit legacy code from time to time and start using PhpSpec straight away for new code. Sometimes it means modifying a bit the boundaries of the existing application to be able to do that. I don’t tend to go back to the code that has already been written, unless this is critical code that needs to be modified anyway to add new functionality. I find that adding acceptance tests before modifying the critical code is easier and an important part of the process.