Download - Mocking Demystified

Transcript
Page 1: Mocking Demystified

Mocking

by @_md

Demystified

Page 2: Mocking Demystified

how many of youwrite tests?

Page 3: Mocking Demystified

how manywrite tests before the code?

Page 4: Mocking Demystified

how many of youknow what a mock is?

Page 5: Mocking Demystified

how many of you use mocks?

Page 6: Mocking Demystified

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

Page 7: Mocking Demystified

test

Arrange

Act

Assert

Page 8: Mocking Demystified

instantiatetested object

test

Arrange

Act

Assert

Page 9: Mocking Demystified

$parser = new MarkdownParser;

Arrangeinstantiatetested object

Page 10: Mocking Demystified

test

Arrange

Act

Assert

run the methodyou want to test

Page 11: Mocking Demystified

$parser = new MarkdownParser;

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

Actrun the methodyou want to test

Page 12: Mocking Demystified

test

Arrange

Act

Assert specify theexpected outcome

Page 13: Mocking Demystified

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

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

Assertspecify theexpected outcome

Page 14: Mocking Demystified

test

Arrange

Act

Assert

instantiate tested object

run the method

check expected outcome

Page 15: Mocking Demystified

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

Page 16: Mocking Demystified

a test is an executable exampleof a class expected behaviour

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

Page 17: Mocking Demystified

READY TO MOCK AROUND?

Page 18: Mocking Demystified

why?

Page 19: Mocking Demystified

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

}

HOW DO I KNOW NOTIFY WORKS?

Page 20: Mocking Demystified

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?

Page 21: Mocking Demystified

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

Page 22: Mocking Demystified

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

[Meszaros 2007]

Page 23: Mocking Demystified

a mock is just 1 ofmany test double patterns

Page 24: Mocking Demystified

doubles are replacement of anything that is not the tested object

Page 25: Mocking Demystified

dummyfakestubmockspy{doubles

[Meszaros 2007]

Page 26: Mocking Demystified

dummyfakestubmockspy{doubles

[Meszaros 2007]

no behaviour

control indirect output

check indirect output

Page 27: Mocking Demystified

a mockist TDD, London school, approach:

only the tested object is real

Page 28: Mocking Demystified

DOUBLES WITHOUT BEHAVIOUR

Page 30: Mocking Demystified

test

Arrange

Act

Assert

Page 31: Mocking Demystified

instantiate (may require collaborators){arrange

Page 32: Mocking Demystified

class MarkdownParser{ private $eventDispatcher;

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

USE DOUBLES TO BYPASS TYPE HINTING

Page 33: Mocking Demystified

class MarkdownParser{ private $eventDispatcher;

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

USE DOUBLES TO BYPASS TYPE HINTING

Page 34: Mocking Demystified

class MarkdownParserTest extends TestCase{

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

}

XUNIT EXAMPLE

Page 35: Mocking Demystified

class MarkdownParser extends ObjectBehavior{

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

}

PHPSPEC EXAMPLE

Page 36: Mocking Demystified

“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]

Page 38: Mocking Demystified

DOUBLES TO CONTROL INDIRECT OUPUT

Page 39: Mocking Demystified

instantiate (may require collaborators)further change state{arrange

Page 40: Mocking Demystified

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

Page 41: Mocking Demystified

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

Page 42: Mocking Demystified

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

Page 43: Mocking Demystified

class EndOfListListener extends EventListener{

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

}

INDIRECT OUTPUTS

Page 44: Mocking Demystified

class EndOfListListener extends EventListener{

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

}

INDIRECT OUTPUTS

Page 45: Mocking Demystified

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

Page 46: Mocking Demystified

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

STUBBING: CONTROLLING DOUBLES INDIRECT OUTPUTS

Page 47: Mocking Demystified

IN PHPSPEC

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

Page 48: Mocking Demystified

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("");

}

Page 49: Mocking Demystified

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>");

}

Page 50: Mocking Demystified

class EndOfListListener extends EventListener{

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

}

THE CODE AGAIN

Page 51: Mocking Demystified

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>");

}

Page 52: Mocking Demystified

loose demandfakestub

returns null to any method call

double behaviour

returns null to defined set methods

returns value defined or raise error

Page 53: Mocking Demystified

DOUBLES TO SPEC MESSAGE EXCHANGE

Page 54: Mocking Demystified

we said before

a test is an executable exampleof a class expected behaviour

Page 55: Mocking Demystified

what is behaviour?

Page 56: Mocking Demystified

B = I + O

behaviour = input + output

Page 57: Mocking Demystified

what can happen in a method?

Page 58: Mocking Demystified

return a value modify stateprint somethingthrow an exceptiondelegate{methods

Page 59: Mocking Demystified

return a value modify stateprint somethingthrow an exceptiondelegate{methods

not the final behaviour

Page 60: Mocking Demystified

return a value print somethingthrow an exceptiondelegate{methods

we should probably delegate that too

Page 61: Mocking Demystified

return a value throw an exceptiondelegate{methods

Page 62: Mocking Demystified

returns a value throws an exception

delegates

method strategy

Page 63: Mocking Demystified

returns a value throws an exception

delegates

assertions/expectations

mocks & spies

method strategy

Page 64: Mocking Demystified

“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

Page 65: Mocking Demystified

messaging

Page 66: Mocking Demystified

yeah, but what does it mean?

Page 67: Mocking Demystified

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

Page 68: Mocking Demystified

Inappropriate Intimacy

Page 69: Mocking Demystified

$this->person->startCar();

Page 70: Mocking Demystified

tell, don’t ask!

[Sharp 1997]

Page 71: Mocking Demystified

exposing lower level implementationmakes the code rigid

Page 72: Mocking Demystified

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

Page 73: Mocking Demystified

focus on messagingmakes the code flexible

Page 74: Mocking Demystified

$this->person->startCar();

Page 75: Mocking Demystified

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/

Page 76: Mocking Demystified

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

}

HOW DO I KNOW NOTIFY WORKS?

Page 77: Mocking Demystified

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

}

HOW DO I KNOW NOTIFY WORKS?

Page 78: Mocking Demystified

use mocks to describe how your method will impact collaborators

Page 79: Mocking Demystified

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

Page 80: Mocking Demystified

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

Page 81: Mocking Demystified

github.com/padraicb/mockery

Page 82: Mocking Demystified

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

Page 83: Mocking Demystified

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

Page 84: Mocking Demystified

use spies to describe indirect output expectations, retrospectively

Page 85: Mocking Demystified

github.com/dancras/doubles

Page 86: Mocking Demystified

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

Page 87: Mocking Demystified
Page 88: Mocking Demystified

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

Page 89: Mocking Demystified

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

Page 90: Mocking Demystified

dummyfakestubmockspy{doubles

[Meszaros 2007]

no behaviour

control indirect output

messaging

Page 91: Mocking Demystified

I work here

I contribute here

I tweet here @_md

Marcello Duarte

Page 92: Mocking Demystified

Thank you !

Page 93: Mocking Demystified

Questions or Comments?

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


Top Related