The need for small and cohesive interfaces is particularly perceived while mocking a class. We typically want to test in isolation a unit and write mocks for its collaborators without going mad.
Let's see an example of a class I may want to mock:
class NakedEntity { public function getMethods() { return $this->_class->getMethods(); } public function getMethod($name) { $methods = $this->_class->getMethods(); return $methods[$name]; } public function hasMethod($name) { $methods = $this->_class->getMethods(); return isset($methods[$name]); } // other methods, constructor... }As I said earlier, mocking is effective if there is a small interface to mock. Note that every class defines an implicit interface: the set of its public methods. Sometimes the interface comprehends several methods that give access to the same data or behavior, and that have to be present to avoid abstraction inversion. In this particular case, if I defined only getMethods() to conserve a small and cohesive interface, every class that depends on NakedEntity would have to implement the other two missing methods.
Mocking all three methods of NakedEntity in phpunit means writing this:
$mock = $this->getMock('NakedEntity'); $mock->expects($this->any()) ->method('getMethods') ->will($this->returnValue(array('doSomething' => ..., 'foo' => ...))); $mock->expects($this->any()) ->method('getMethod') ->will($this->returnValue(...)); $mock->expects($this->any()) ->method('hasMethod') ->will($this->returnValue(true));Compare this to the creation of a real NakedEntity. I should definitely create a real object to save test code, but the unit tests will then not be executed in isolation and I will have to define a mocked NakedClass object (the $this->_class property) and break the Law of Demeter.
Moreover, the mocking capabilities of phpunit are limited and for example we cannot define different return values based on the parameters (a real subclass is needed in that case) without a callback. I could mock only the methods effectively used from the SUT, but I don't really know which of them are really called (since they are more or less equivalent) and I want to refactor the SUT without changing the tests.
So I found a 2-step alternative solution.
Step 1: convenience methods become template methods
I started with refactoring the NakedEntity class:
class NakedEntity { public function getMethods() { return $this->_class->getMethods(); } public function getMethod($name) { $methods = $this->getMethods(); return $methods[$name]; } public function hasMethod($name) { $methods = $this->getMethods(); return isset($methods[$name]); } // other methods, constructor... }The users of getMethods() are now template methods, and the base method (primitive operation in design patterns jargon) can be subclassed to provide alternative behavior. The subclass can be implemented as a real reusable class, which will include a setMethods() utility method (no pun intended), or via mocking.
Step 2: mock the base method
Now only getMethods() need to be substituted:
$mock = $this->getMock('NakedEntity', array('getMethods')); $mock->expects($this->any()) ->method('getMethods') ->will($this->returnValue(array('doSomething' => $myMethod, 'foo' => ...)));
$this->assertEquals($myMethod, $mock->getMethod('doSomething'));
This approach works well because the contract of NakedEntity is already cohesive and the different methods provide different ways to do the same thing. The template methods contain nearly no logic and they are exercised in unit tests which are not their own: it is a very small trade-off because it is highly improbable that they will break and cause another class unit tests to fail without reasons. The template methods in this case are only glue code.
Don't use this testing pattern as an excuse to write many public methods: you should indeed break up a class in different units if its contract grows too much. You can implement a Decorator pattern if convenience template methods are implementing business logic on a public method, or it may be the case that your class is doing too much and the Api is too complicated. Another viable solution if you have an explicit interface instead of a concrete class is creating a reusable Fake implementation which will contain the convenience methods as well.
In conclusion, if you have a contract with many cohesive and dumb methods, which relies on a central one to provide data, you can create template methods and reuse them in other unit tests, via subclassing of the primitive operations.
2 comments:
For another mighty yet easy to use PHP mocking library see Lime 2 alpha that has just been released :)
http://www.symfony-project.org/blog/2009/11/10/lime-2-alpha-released
Thanks bernhard, alternatives and competition are good.
Post a Comment