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.
19. class ParserSubject
{
public function notify(Event $event)
{
foreach ($this->subscribers as $subscriber) {
$subscriber->onChange($event);
}
}
}
HOW DO I KNOW NOTIFY WORKS?
20. 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?
21. 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
22. {
focus on unit
expensive to instantiate
why? control on collaborators’ state
indirect outputs
undesirable side effects
[Meszaros 2007]
32. class MarkdownParser
{
private $eventDispatcher;
public function __construct(EventDispatcher $dispatcher)
{
$this->eventDispatcher = $dispatcher;
}
}
USE DOUBLES TO BYPASS TYPE HINTING
33. class MarkdownParser
{
private $eventDispatcher;
public function __construct(EventDispatcher $dispatcher)
{
$this->eventDispatcher = $dispatcher;
}
}
USE DOUBLES TO BYPASS TYPE HINTING
34. class MarkdownParserTest extends TestCase
{
function setUp()
{
$dispatcher = $this->getMock('EventDispatcher');
$this->parser = new MardownParser($dispatcher);
}
}
XUNIT EXAMPLE
35. class MarkdownParser extends ObjectBehavior
{
function let($dispatcher)
{
$dispatcher->beAMockOf('EventDispatcher');
$this->beConstructedWith($dispatcher);
}
}
PHPSPEC EXAMPLE
39. arrange
{
instantiate (may require collaborators)
further change state
40. 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
41. 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
42. arrange
{
instantiate (may require collaborators)
further change state
configure indirect output
43. class EndOfListListener extends EventListener
{
public function onNewLine(Event $event)
{
$html = $event->getText();
if ($this->document->getNextLine() == "") {
$html .= "</li></ul>";
}
return $html;
}
}
INDIRECT OUTPUTS
44. class EndOfListListener extends EventListener
{
public function onNewLine(Event $event)
{
$html = $event->getText();
if ($this->document->getNextLine() == "") {
$html .= "</li></ul>";
}
return $html;
}
}
INDIRECT OUTPUTS
45. fake
stub
doubles for controlling indirect output
http://www.flickr.com/photos/fcharlton/1841638596/
http://www.flickr.com/photos/64749744@N00/476724045
46. $event->getText(); // will return “Hello World”
$this->document->getNextLine(); // will return “”
STUBBING: CONTROLLING DOUBLES INDIRECT OUTPUTS
47. function let($event, $document)
{
$event->beAMockOf("Event");
$document->beAMockOf("Document");
$this->beConstructedWith($document);
}
IN PHPSPEC
48. 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
49. 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>");
}
IN PHPSPEC
50. class EndOfListListener extends EventListener
{
public function onNewLine(Event $event)
{
$html = $event->getText();
if ($this->document->getNextLine() == "") {
$html .= "</li></ul>";
}
return $html;
}
}
THE CODE AGAIN
51. 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>");
}
THE SPEC AGAIN
52. double behaviour
loose demand returns null to any method call
fake returns null to defined set methods
stub returns value defined or raise error
58. {
return a value
modify state
methods print something
throw an exception
delegate
59. {
return a value
not the final
modify state
behaviour
methods print something
throw an exception
delegate
60. {
methods
return a value we should probably
print something delegate that too
throw an exception
delegate
61. {
methods
return a value
throw an exception
delegate
62. method strategy
returns a value
throws an exception
delegates
63. method strategy
returns a value assertions/
throws an exception expectations
delegates mocks & spies
64. Viewpoints Research Institute Source - Bonnie Macbird URL -http://www.vpri.org
“The key in making great and growable systems is much more to
design how its modules communicate
rather than what their internal properties
and behaviours should be.”
Messaging
75. mock
spy
doubles for messaging
http://www.flickr.com/photos/scribe215/3234974208/
http://www.flickr.com/photos/21560098@N06/5772919201/
76. class ParserSubject
{
public function notify(Event $event)
{
foreach ($this->subscribers as $subscriber) {
$subscriber->onChange($event);
}
}
}
HOW DO I KNOW NOTIFY WORKS?
77. class ParserSubject
{
public function notify(Event $event)
{
foreach ($this->subscribers as $subscriber) {
$subscriber->onChange($event);
}
}
}
HOW DO I KNOW NOTIFY WORKS?
78. use mocks to describe how your
method will impact collaborators
79. 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