Mocking Demystified

Post on 10-May-2015

4,549 views 3 download

Tags:

description

Replacing dependents with doubles is a central part of testing that every developer has to master. This talk goes over the different types of doubles and explains their place in testing, how to implement them in a mainstream mocking framework, and which strategies or doubles to use in different message exchange scenarios between objects. After this talk you will have moved a step forward in your understanding of testing in the context of object oriented programming.

transcript

Mocking

by @_md

Demystified

how many of youwrite tests?

how manywrite tests before the code?

how many of youknow what a mock is?

how many of you use mocks?

ALL YOU NEED TO KNOW ABOUT TESTING(in 5 minutes)

test

Arrange

Act

Assert

instantiatetested object

test

Arrange

Act

Assert

$parser = new MarkdownParser;

Arrangeinstantiatetested object

test

Arrange

Act

Assert

run the methodyou want to test

$parser = new MarkdownParser;

$html = $parser->toHtml('Hello World');

Actrun the methodyou want to test

test

Arrange

Act

Assert specify theexpected outcome

$parser = new MarkdownParser;$html = $parser->toHtml('Hello World');

assertTrue('<p>Hello World</p>' == $html);

Assertspecify theexpected outcome

test

Arrange

Act

Assert

instantiate tested object

run the method

check expected outcome

$this->toHtml('Hello World') ->shouldReturn('<p>Hello World</p>');

a test is an executable exampleof a class expected behaviour

$this->toHtml('Hello World') ->shouldReturn('<p>Hello World</p>');

READY TO MOCK AROUND?

why?

class ParserSubject{ public function notify(Event $event) { foreach ($this->subscribers as $subscriber) { $subscriber->onChange($event); } }

}

HOW DO I KNOW NOTIFY WORKS?

class ParserSubjectTest extends TestCase{ /** @test */ function it_notifies_subscribers() { // arrange $parser = new ParserSubject; // act // I need an event!!! $parser->notify(/* $event ??? */);

// assert // how do I know subscriber::onChange got called? }

}

HOW DO I KNOW NOTIFY WORKS?

class EndOfListListener extends EventListener{

public function onNewLine(Event $event) { $html = $event->getText(); if ($this->document->getNextLine() == "") { $html .= "</li></ul>"; } return $html; }

}

HOW DO I CONTROL EVENT AND DOCUMENT

focus on unitexpensive to instantiatecontrol on collaborators’ stateindirect outputsundesirable side effects{why?

[Meszaros 2007]

a mock is just 1 ofmany test double patterns

doubles are replacement of anything that is not the tested object

dummyfakestubmockspy{doubles

[Meszaros 2007]

dummyfakestubmockspy{doubles

[Meszaros 2007]

no behaviour

control indirect output

check indirect output

a mockist TDD, London school, approach:

only the tested object is real

DOUBLES WITHOUT BEHAVIOUR

test

Arrange

Act

Assert

instantiate (may require collaborators){arrange

class MarkdownParser{ private $eventDispatcher;

public function __construct(EventDispatcher $dispatcher) { $this->eventDispatcher = $dispatcher; }}

USE DOUBLES TO BYPASS TYPE HINTING

class MarkdownParser{ private $eventDispatcher;

public function __construct(EventDispatcher $dispatcher) { $this->eventDispatcher = $dispatcher; }}

USE DOUBLES TO BYPASS TYPE HINTING

class MarkdownParserTest extends TestCase{

function setUp() { $dispatcher = $this->getMock('EventDispatcher'); $this->parser = new MardownParser($dispatcher); }

}

XUNIT EXAMPLE

class MarkdownParser extends ObjectBehavior{

function let($dispatcher) { $dispatcher->beAMockOf('EventDispatcher'); $this->beConstructedWith($dispatcher); }

}

PHPSPEC EXAMPLE

“Dummy is a placeholder passed to the SUT (tested object), but never used”

http

://w

icke

r123

.dev

iant

art.c

om/a

rt/S

lapp

y-T

he-D

umm

y-14

8136

425

[Meszaros 2007]

DOUBLES TO CONTROL INDIRECT OUPUT

instantiate (may require collaborators)further change state{arrange

class MarkdownParserTest extends TestCase{

function setUp() { $dispatcher = $this->getMock('EventDispatcher'); $this->parser = new MardownParser($dispatcher);

$this->parser->setEncoding('UTF-8');

}

}

FURTHER CHANGE TO THE STATE IN ARRANGE

class MarkdownParserTest extends TestCase{

function setUp() { $dispatcher = $this->getMock('EventDispatcher'); $this->parser = new MardownParser($dispatcher);

$this->parser->setEncoding('UTF-8');

}

}

FURTHER CHANGE TO THE STATE IN ARRANGE

instantiate (may require collaborators)further change stateconfigure indirect output{arrange

class EndOfListListener extends EventListener{

public function onNewLine(Event $event) { $html = $event->getText(); if ($this->document->getNextLine() == "") { $html .= "</li></ul>"; } return $html; }

}

INDIRECT OUTPUTS

class EndOfListListener extends EventListener{

public function onNewLine(Event $event) { $html = $event->getText(); if ($this->document->getNextLine() == "") { $html .= "</li></ul>"; } return $html; }

}

INDIRECT OUTPUTS

doubles for controlling indirect output

fake stub

http

://w

ww

.flic

kr.c

om/p

hoto

s/fc

harl

ton/

1841

6385

96/

htt

p://w

ww

.flic

kr.c

om/p

hoto

s/64

7497

44@

N00

/476

7240

45

$event->getText(); // will return “Hello World”$this->document->getNextLine(); // will return “”

STUBBING: CONTROLLING DOUBLES INDIRECT OUTPUTS

IN PHPSPEC

function let($event, $document){ $event->beAMockOf("Event"); $document->beAMockOf("Document"); $this->beConstructedWith($document);}

IN PHPSPEC

function let($event, $document){ $event->beAMockOf("Event"); $document->beAMockOf("Document"); $this->beConstructedWith($document);}

function it_ends_list_when_next_is_empty($event, $document){ $event->getText()->willReturn("Some text"); $document->getNextLine()->willReturn("");

}

IN PHPSPEC

function let($event, $document){ $event->beAMockOf("Event"); $document->beAMockOf("Document"); $this->beConstructedWith($document);}

function it_ends_list_when_next_is_empty($event, $document){ $event->getText()->willReturn("Some text"); $document->getNextLine()->willReturn("");

$this->onNewLine($event) ->shouldReturn("Some text</li></ul>");

}

class EndOfListListener extends EventListener{

public function onNewLine(Event $event) { $html = $event->getText(); if ($this->document->getNextLine() == "") { $html .= "</li></ul>"; } return $html; }

}

THE CODE AGAIN

THE SPEC AGAIN

function let($event, $document){ $event->beAMockOf("Event"); $document->beAMockOf("Document"); $this->beConstructedWith($document);}

function it_ends_list_when_next_is_empty($event, $document){ $event->getText()->willReturn("Some text"); $document->getNextLine()->willReturn("");

$this->onNewLine($event) ->shouldReturn("Some text</li></ul>");

}

loose demandfakestub

returns null to any method call

double behaviour

returns null to defined set methods

returns value defined or raise error

DOUBLES TO SPEC MESSAGE EXCHANGE

we said before

a test is an executable exampleof a class expected behaviour

what is behaviour?

B = I + O

behaviour = input + output

what can happen in a method?

return a value modify stateprint somethingthrow an exceptiondelegate{methods

return a value modify stateprint somethingthrow an exceptiondelegate{methods

not the final behaviour

return a value print somethingthrow an exceptiondelegate{methods

we should probably delegate that too

return a value throw an exceptiondelegate{methods

returns a value throws an exception

delegates

method strategy

returns a value throws an exception

delegates

assertions/expectations

mocks & spies

method strategy

“The key in making great and growable systems is much more to

design how its modules communicate

rather than what their internal propertiesand behaviours should be.”

Messaging

View

poin

ts R

esea

rch

Inst

itute

Sou

rce

- Bon

nie

Mac

bird

UR

L -h

ttp://

ww

w.vp

ri.or

g

messaging

yeah, but what does it mean?

$this->person->getCar()->getEngine()->ignite();

Inappropriate Intimacy

$this->person->startCar();

tell, don’t ask!

[Sharp 1997]

exposing lower level implementationmakes the code rigid

$this->person->getCar()->getEngine()->ignite();

focus on messagingmakes the code flexible

$this->person->startCar();

doubles for messaging

http

://w

ww

.flic

kr.c

om/p

hoto

s/21

5600

98@

N06

/577

2919

201/

mock spy

http

://w

ww

.flic

kr.c

om/p

hoto

s/sc

ribe

215/

3234

9742

08/

class ParserSubject{ public function notify(Event $event) { foreach ($this->subscribers as $subscriber) { $subscriber->onChange($event); } }

}

HOW DO I KNOW NOTIFY WORKS?

class ParserSubject{ public function notify(Event $event) { foreach ($this->subscribers as $subscriber) { $subscriber->onChange($event); } }

}

HOW DO I KNOW NOTIFY WORKS?

use mocks to describe how your method will impact collaborators

use PHPUnit_Framework_TestCase as TestCase;

class ParserSubjectTest extends TestCase{ /** @test */ function it_notifies_subscribers() { $event = $this->getMock("Event");

$subscriber = $this->getMock("Subscriber"); $subscriber->expects($this->once()) ->method("onChange") ->with($event);

$parser = new ParserSubject(); $parser->attach($subscriber); $parser->notify($event); }}

USING PHPUNIT NATIVE MOCK CONSTRUCT

public function testUpdateWithEqualTypes(){ $installer = $this->createInstallerMock(); $manager = new InstallationManager('vendor'); $manager->addInstaller($installer);

$initial = $this->createPackageMock(); $target = $this->createPackageMock(); $operation = new UpdateOperation($initial, $target, 'test');

$initial ->expects($this->once()) ->method('getType') ->will($this->returnValue('library')); $target ->expects($this->once()) ->method('getType') ->will($this->returnValue('library'));

$installer ->expects($this->once()) ->method('supports') ->with('library') ->will($this->returnValue(true));

$installer ->expects($this->once()) ->method('update') ->with($this->repository, $initial, $target);

EXCESSIVE SETUP

github.com/padraicb/mockery

use PHPUnit_Framework_TestCase as TestCase;

class ParserSubjectTest extends TestCase{ /** @test */ function it_notifies_subscribers() { $event = Mockery::mock("Event");

$subscriber = Mockery::mock("Subscriber"); $subscriber->shouldReceive("onChange") ->with($event);

$parser = new ParserSubject(); $parser->attach($subscriber); $parser->notify($event); }}

USING MOCKERY

class ParserSubject extends PHPSpec2\ObjectBehavior{ /** * @param Event $event * @param Subscriber $subscriber */ function it_notifies_subscribers($event, $subscriber) { $subscriber->onChange($event)->shouldBeCalled(); $this->attach($subscriber); $this->notify($event); }}

SAME EXAMPLE IN PHPSPEC

use spies to describe indirect output expectations, retrospectively

github.com/dancras/doubles

use PHPSpec2\ObjectBehavior;

class ParserSubject extends ObjectBehavior{ function it_notifies_subscribers() { $event = Doubles::fromClass('\Event'); $subscriber = Doubles::fromClass('\Subscriber'); $this->attach($subscriber); $this->notify($event);

$subscriber->spy('onChange')->callCount()->shoudBe(1);

}}

SPY EXAMPLE – WITH “DOUBLES” FRAMEWORK

use PHPSpec2\ObjectBehavior;

class ParserSubject extends ObjectBehavior{ /** * @param Event $event * @param Subscriber $subscriber */ function it_notifies_subscribers($event, $subscriber) { $this->attach($subscriber); $this->notify($event); $subscriber->onChange($event) ->shouldHaveBeenCalled(); }}

SPY EXAMPLE – AVAILABLE WITH PROPHECY INTEGRATION

use Prophecy\Prophet;

class TestCase extends SomeUnitTestingFrameworkTestCase{ function createDoubler($name) { $this->prophet = new \Prophecy\Prophet; return $this->prophet->prophesize($name); }

function createDummy($name) { return $this->createDoubler($name)->reveal(); }}

INTEGRATING PROPHECY

dummyfakestubmockspy{doubles

[Meszaros 2007]

no behaviour

control indirect output

messaging

I work here

I contribute here

I tweet here @_md

Marcello Duarte

Thank you !

Questions or Comments?

want to improve on testing? bit.ly/inviqa-bdd-training