2011年1月2日 星期日

Mockery: From Mock Objects to Test Spies

With next week seeing the formal release of Mockery 0.6 which is currently fermenting on Github at http://github.com/padraic/mockery, I’m already looking forward to next piece of the puzzle arriving with 0.7. Mockery is an opportunity to being something new and fresh to the PHP mock objects environment beyond a far neater flexible API.

If you follow the test double debate in other languages there are two popular concepts making their presence felt. The first is that the over-specified definitions of test doubles (we have dummy objects, test stubs, mock objects, test spies and fake objects) do more harm than good since in reality we never really distinguish (nor care to) between most of them. In most cases we just sort of wing it, using our own concepts and intent to create test doubles which do what we want with little thought as to the kind of test double we’re creating. In the end, the definitions all merge together into a spectrum of possible behaviours we add as and if needed.

The second is that we’re seeing a lot more attention over whether Mock Objects are the best way to do things. Nobody doubts they have the right idea with Mock Objects, but the upfront setting of expectations may come across as unintuitive. A typical test begins with exercising code, and thereafter we typically make assertions on the behaviour (whether it be a resulting state change or an object interaction). Mock Objects reverse that – we expect first, and then we exercise the code.

With the future (before end of June) 0.7 release, Mockery will offer an alternative approach to the traditional Expect-Exercise-Verify cycle called the Test Spy. 0.6 already offers a measure of that capability through its ability to record interactions, though recording is completely automated and geared primarily towards comparing two sets of source code which should behave identically (e.g. during refactoring). The new Test Spy implementation will be API driven, using a similar form to Mockery’s existing Mock Object approach.

Why Test Spies?

Using Test Spies returns to the test based approach of exercising code first, and then making assertions. There’s a reason why this cyclical approach is used in unit testing. It’s easy to understand and makes sense from the start. Applying it to what we do with Mock Objects can make using Test Doubles in general easier to learn and teach. It may also remove barriers to entry for those who simply never understood Mock Objects or loathed their expectation setup. Test Spies also operate on a selective basis – where Mock Objects require upfront expectations to define how they will behave, Test Spies just do what you need them to and then allow selective assertions afterwards (i.e. you only need to test what you want to test).

The difference, once you see the code, is very subtle. Not unusually for testing practices, subtle differences in approach rely on the YMMV yardstick. Some people prefer the upfront expectation setting, others find the after-use assertion approach easier to understand or as more applicable to their thinking.

By supporting both approaches as equal citizens, Mockery once again lets go of the proverbial rope around your neck tied there by Java traditions. You’ll be free to go with whatever approach you find yourself preferring. Better, they can both co-exist in relative peace since they are drawn from the same framework and both share nearly all of the exact same API elements, with the exact same meaning. Whether you prefer Mock Objects or Test Spies (both equally are capable of being used as Test Stubs), they are both equally understandable by anyone using Mockery.

As a means of showing the differences, here are two test cases. The first is written using the Mock Object terminology, the second using the Test Spy terminology – each actually achieves the exact same goal.

[geshi lang=php]use \Mockery as M;

class StarshipTest extends PHPUnit_Framework_TestCase
{

public function testEngagingWarpDrive()
{
$engineering = M::mock(‘Engineering’);
$engineering->shouldReceive(‘prepForWarpFactor’)->with(8)->once()->andReturn(true);
$engineering->shouldReceive(‘engage’)->once();

$starship = new Starship($engineering);
$starship->accelerateToWarp(8);
}

}[/geshi]

Or, using a Test Spy approach.

[geshi lang=php]use \Mockery as M;

class StarshipTest extends PHPUnit_Framework_TestCase
{

public function testEngagingWarpDrive()
{
$engineering = M::mock(‘Engineering’);
$engineering->whenReceives(‘prepForWarpFactor’)->thenReturn(true);

$starship = new Starship($engineering);
$starship->accelerateToWarp(8);

$engineering->assertReceived(‘prepForWarpFactor’)->with(8)->once();
$engineering->assertReceived(‘engage’)->once();
}

}[/geshi]

To demonstrate the selective assertion approach, what if we just didn’t care about whether or not the engage() method was used? In that case, we could just drop it from the assertions altogether – never mentioning it in the test at all. In the Mock Object approach, we cannot do this – mocking means we must set all methods expected (otherwise the methods would not exist on the mock). We could even drop the assertion on prepForWarpFactor, although we still need to retain the stubbing of its return value (since it’s needed).

Supporting Test Spies and Mock Objects also has the other obvious benefit that you can switch modes effortlessly. There will always be cases where expectation setting is preferable over assertions and vice versa. Both have their uses even if you heavily prefer any one over the other.

Watch for the Mockery 0.6 release next week! I’ll follow through with Test Spies in 0.7 once I get the final API down (working on making it even shorter than our Mock Object API can achieve).

reference : http://blog.astrumfutura.com/2010/05/mockery-from-mock-objects-to-test-spies/

沒有留言:

wibiya widget