View
4.556
Download
3
Category
Tags:
Preview:
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.
Citation preview
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
http
://w
icke
r123
.dev
iant
art.c
om/a
rt/S
lapp
y-T
he-D
umm
y-14
8136
425
dummy
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]
dummy: double with no behaviour
http
://w
icke
r123
.dev
iant
art.c
om/a
rt/S
lapp
y-T
he-D
umm
y-14
8136
425
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
Recommended