SlideShare ist ein Scribd-Unternehmen logo
1 von 103
Downloaden Sie, um offline zu lesen
Design how your
objects talk
through mocking
”
– Reverse Focus on the reverse mortgages
“One of the most common mistakes people
make is to fixate on the goal or expected
outcome while ignoring their underlying
behaviours.”
@everzet
• BDD Practice Manager

• Software Engineer

• Creator of Behat, Mink,
Prophecy, PhpSpec2

• Contributor to Symfony2,
Doctrine2, Composer
This talk is about
• Test-driven development with and without mocks

• Introducing and making sense of different types of
doubles

• OOP as a messaging paradigm

• Software design as a response to messaging
observations

• Code
Test-driven
development
By Example

!
“The TDD book”

!
Circa 2002
Money multiplication test from the TDD book
public void testMultiplication()
{
Dollar five = new Dollar(5);
Dollar product = five.times(2);
!
assertEquals(10, product.amount);
!
product = five.times(3);
!
assertEquals(15, product.amount);
}
Money multiplication test in PHP
public function testMultiplication()
{
$five = new Dollar(5);
$product = $five->times(2);
$this->assertEquals(10, $product->getAmount());
$product = $five->times(3);
$this->assertEquals(15, $product->getAmount());
}
Event dispatching test
public function testEventIsDispatchedDuringRegistration()
{
$dispatcher = new EventDispatcher();
$repository = new UserRepository();
$manager = new UserManager($repository, $dispatcher);
!
$timesDispatched = 0;
$dispatcher->addListener(
'userIsRegistered',
function() use($timesDispatched) {
$timesDispatched += 1;
}
);
!
$user = User::signup('ever.zet@gmail.com'); 
$manager->registerUser($user);
 
$this->assertSame(1, $timesDispatched);
}
Event dispatching test
public function testEventIsDispatchedDuringRegistration()
{
$dispatcher = new EventDispatcher();
$repository = new UserRepository();
$manager = new UserManager($repository, $dispatcher);
!
$timesDispatched = 0;
$dispatcher->addListener(
'userIsRegistered',
function() use($timesDispatched) {
$timesDispatched += 1;
}
);
!
$user = User::signup('ever.zet@gmail.com'); 
$manager->registerUser($user);
 
$this->assertSame(1, $timesDispatched);
}
”
– Ralph Waldo Emerson
“Life is a journey, not a destination.”
Growing
Object-Oriented
Software,
Guided by Tests

!
“The GOOS book”

!
Circa 2009
Event dispatching test
public function testEventIsDispatchedDuringRegistration()
{
$dispatcher = new EventDispatcher();
$repository = new UserRepository();
$manager = new UserManager($repository, $dispatcher);
!
$timesDispatched = 0;
$dispatcher->addListener(
'userIsRegistered',
function() use($timesDispatched) {
$timesDispatched += 1;
}
);
!
$user = User::signup('ever.zet@gmail.com'); 
$manager->registerUser($user);
 
$this->assertSame(1, $timesDispatched);
}
Event dispatching collaborators
public function testEventIsDispatchedDuringRegistration()
{
$dispatcher = new EventDispatcher();
$repository = new UserRepository();
$manager = new UserManager($repository, $dispatcher);
!
$timesDispatched = 0;
$dispatcher->addListener(
'userIsRegistered',
function() use($timesDispatched) {
$timesDispatched += 1;
}
);
!
$user = User::signup('ever.zet@gmail.com'); 
$manager->registerUser($user);
 
$this->assertSame(1, $timesDispatched);
}
Find the message
public function testEventIsDispatchedDuringRegistration()
{
$dispatcher = new EventDispatcher();
$repository = new UserRepository();
$manager = new UserManager($repository, $dispatcher);
!
$timesDispatched = 0;
$dispatcher->addListener(
'userIsRegistered',
function() use($timesDispatched) {
$timesDispatched += 1;
}
);
!
$user = User::signup('ever.zet@gmail.com'); 
$manager->registerUser($user);
 
$this->assertSame(1, $timesDispatched);
}
messages or state
”
– Alan Kay, father of OOP
“OOP to me means only messaging, local
retention and protection and hiding of state-
process, and extreme late-binding of all things.”
Interfaces
interface LoginMessenger {
public function askForCard();
public function askForPin();
}
 
interface InputMessenger {
public function askForAccount();
public function askForAmount();
}
 
interface WithdrawalMessenger {
public function tellNoMoney();
public function tellMachineEmpty();
}
Doubles
1. Dummy

2. Stub

3. Spy

4. Mock

5. Fake
Prophecy
(1) use ProphecyProphet;
(2) use ProphecyArgument;
(3) $prophet = new Prophet();
(4) $userProphecy = $prophet->prophesize(UserInterface::class);
(5) $userProphecy->changeName('everzet')->shouldBeCalled();
(6) $user = $userProphecy->reveal();
(7) $user->changeName('_md');
(8) $prophet->checkPredictions();
1. Dummy
1. Dummy
class System {
private $authorizer;
 
public function __construct(Authorizer $authorizer) {
$this->authorizer = $authorizer;
}
 
public function getLoginCount() {
return 0;
}
}
1. Dummy
class System {
private $authorizer;
 
public function __construct(Authorizer $authorizer) {
$this->authorizer = $authorizer;
}
 
public function getLoginCount() {
return 0;
}
}
!
public function testNewlyCreatedSystemHasNoLoggedInUsers() {
$auth = $this->prophesize(Authorizer::class);
$system = new System($auth->reveal());
$this->assertSame(0, $system->getLoginCount());
}
1. Dummy
class System {
private $authorizer;
 
public function __construct(Authorizer $authorizer) {
$this->authorizer = $authorizer;
}
 
public function getLoginCount() {
return 0;
}
}
!
public function testNewlyCreatedSystemHasNoLoggedInUsers() {
$auth = $this->prophesize(Authorizer::class);
$system = new System($auth->reveal());
$this->assertSame(0, $system->getLoginCount());
}
2. Stub
2. Stub
class System {
// ...
!
public function logIn($username, $password) {
if ($this->authorizer->authorize($username, $password)) {
$this->loginCount++;
}
}
!
public function getLoginCount() {
return $this->loginCount;
}
}
2. Stub
class System {
// ...
!
public function logIn($username, $password) {
if ($this->authorizer->authorize($username, $password)) {
$this->loginCount++;
}
}
!
public function getLoginCount() {
return $this->loginCount;
}
}
!
public function testCountsSuccessfullyAuthorizedLogIns() {
$auth = $this->prophesize(Authorizer::class);
$system = new System($auth->reveal());
!
$auth->authorize('everzet', '123')->willReturn(true);
!
$system->logIn('everzet', ‘123’);
!
$this->assertSame(1, $system->getLoginCount());
}
2. Stub
class System {
// ...
!
public function logIn($username, $password) {
if ($this->authorizer->authorize($username, $password)) {
$this->loginCount++;
}
}
!
public function getLoginCount() {
return $this->loginCount;
}
}
!
public function testCountsSuccessfullyAuthorizedLogIns() {
$auth = $this->prophesize(Authorizer::class);
$system = new System($auth->reveal());
!
$auth->authorize('everzet', '123')->willReturn(true);
!
$system->logIn('everzet', ‘123’);
!
$this->assertSame(1, $system->getLoginCount());
}
2. Stub
class System {
// ...
!
public function logIn($username, $password) {
if ($this->authorizer->authorize($username, $password)) {
$this->loginCount++;
}
}
!
public function getLoginCount() {
return $this->loginCount;
}
}
!
public function testCountsSuccessfullyAuthorizedLogIns() {
$auth = $this->prophesize(Authorizer::class);
$system = new System($auth->reveal());
!
$auth->authorize('everzet', '123')->willReturn(true);
!
$system->logIn(‘_md', ‘321’);
!
$this->assertSame(1, $system->getLoginCount());
}
3. Spy
3. Spy
class System {
// ...
!
public function logIn($username, $password) {
if ($this->authorizer->authorize($username, $password)) {
$this->loginCount++;
$this->lastLoginTimer->recordLogin($username);
}
}
}
3. Spy
class System {
// ...
!
public function logIn($username, $password) {
if ($this->authorizer->authorize($username, $password)) {
$this->loginCount++;
$this->lastLoginTimer->recordLogin($username);
}
}
}
!
public function testCountsSuccessfullyAuthorizedLogIns() {
$auth = $this->prophesize(Authorizer::class);
$timer = $this->prophesize(LoginTimer::class);
$system = new System($auth->reveal(), $timer->reveal());
$auth->authorize('everzet', '123')->willReturn(true);
!
$system->login('everzet', '123');
!
$timer->recordLogin('everzet')->shouldHaveBeenCalled();
}
3. Spy
class System {
// ...
!
public function logIn($username, $password) {
if ($this->authorizer->authorize($username, $password)) {
$this->loginCount++;
$this->lastLoginTimer->recordLogin($username);
}
}
}
!
public function testCountsSuccessfullyAuthorizedLogIns() {
$auth = $this->prophesize(Authorizer::class);
$timer = $this->prophesize(LoginTimer::class);
$system = new System($auth->reveal(), $timer->reveal());
$auth->authorize('everzet', '123')->willReturn(true);
!
$system->login('everzet', '123');
!
$timer->recordLogin('everzet')->shouldHaveBeenCalled();
}
4. Mock
3. Spy
class System {
// ...
!
public function logIn($username, $password) {
if ($this->authorizer->authorize($username, $password)) {
$this->loginCount++;
$this->lastLoginTimer->recordLogin($username);
}
}
}
!
public function testCountsSuccessfullyAuthorizedLogIns() {
$auth = $this->prophesize(Authorizer::class);
$timer = $this->prophesize(LoginTimer::class);
$system = new System($auth->reveal(), $timer->reveal());
$auth->authorize('everzet', '123')->willReturn(true);
!
$system->login('everzet', '123');
!
$timer->recordLogin('everzet')->shouldHaveBeenCalled();
}
4. Mock
class System {
// ...
!
public function logIn($username, $password) {
if ($this->authorizer->authorize($username, $password)) {
$this->loginCount++;
$this->lastLoginTimer->recordLogin($username);
}
}
}
!
public function testCountsSuccessfullyAuthorizedLogIns() {
$auth = $this->prophesize(Authorizer::class);
$timer = $this->prophesize(LoginTimer::class);
$system = new System($auth->reveal(), $timer->reveal());
$auth->authorize('everzet', '123')->willReturn(true);
!
$timer->recordLogin('everzet')->shouldBeCalled();
!
$system->login('everzet', '123');
!
$this->getProphet()->checkPredictions();
}
Back to the
event dispatcher
Find the message
public function testEventIsDispatchedDuringRegistration()
{
$dispatcher = new EventDispatcher();
$repository = new UserRepository();
$manager = new UserManager($repository, $dispatcher);
!
$timesDispatched = 0;
$dispatcher->addListener(
'userIsRegistered',
function() use($timesDispatched) {
$timesDispatched += 1;
}
);
!
$user = User::signup('ever.zet@gmail.com'); 
$manager->registerUser($user);
 
$this->assertSame(1, $timesDispatched);
}
Communication over state
public function testEventIsDispatchedDuringRegistration()
{
$repository = $this->prophesize(UserRepository::class);
$dispatcher = $this->prophesize(EventDispatcher::class);
$manager = new UserManager(
$repository->reveal(),
$dispatcher->reveal()
);
 
$user = User::signup('ever.zet@gmail.com'); 
$manager->registerUser($user);
!
$dispatcher->dispatch('userIsRegistered', Argument::any())
->shouldHaveBeenCalled();
}
Exposed communication
public function testEventIsDispatchedDuringRegistration()
{
$repository = $this->prophesize(UserRepository::class);
$dispatcher = $this->prophesize(EventDispatcher::class);
$manager = new UserManager(
$repository->reveal(),
$dispatcher->reveal()
);
 
$user = User::signup('ever.zet@gmail.com'); 
$manager->registerUser($user);
!
$dispatcher->dispatch('userIsRegistered', Argument::any())
->shouldHaveBeenCalled();
}
Design?
”
– The Observer Effect
“The act of observing will influence the
phenomenon being observed.”
The 1st case:
simple controller
Simple Symfony2 controller
public function packagesListAction(Request $req, User $user) {
$packages = $this->getDoctrine()
->getRepository('WebBundle:Package')
->getFilteredQueryBuilder(array('maintainer' => $user->getId()))
->orderBy('p.name')
->getQuery()
->execute();
!
return $this->render('WebBundle:User:packages.html.twig', [
'packages' => $packages
]);
}
“Simple” Symfony2 controller test
public function testShowMaintainedPackages() {
$request = new Request();
$user = new User('everzet');
$container = $this->prophesize(ContainerInterface::class);
$doctrine = $this->prophesize(EntityManager::class);
$repository = $this->prophesize(PackageRepository::class);
$queryBuilder = $this->prophesize(QueryBuilder::class);
$query = $this->prophesize(Query::class);
$packages = [new Package('Behat'), new Package('PhpSpec')];
$templating = $this->prophesize(EngineInterface::class);
$response = new Response('User packages');
!
$container->get('doctrine.orm')->willReturn($doctrine);
$doctrine->getRepository('WebBundle:Package')->willReturn($repository);
$repository->getFilteredQueryBuilder(['maintainer' => $user->getId()])
->willReturn($queryBuilder);
$queryBuilder->orderBy('p.name')->shouldBeCalled();
$queryBuilder->getQuery()->willReturn($query);
$query->execute()->willReturn($packages);
$templating->renderResponse(
'WebBundle:User:packages.html.twig', ['packages' => $packages], null)
->willReturn($response);
!
$controller = new UserController();
$controller->setContainer($container);
$controllerResult = $controller->maintainsPackagesAction($request, $user);
!
$this->assertEquals($response, $controllerResult);
}
“Simple” Symfony2 controller test
public function testShowMaintainedPackages() {
$request = new Request();
$user = new User('everzet');
$container = $this->prophesize(ContainerInterface::class);
$doctrine = $this->prophesize(EntityManager::class);
$repository = $this->prophesize(PackageRepository::class);
$queryBuilder = $this->prophesize(QueryBuilder::class);
$query = $this->prophesize(Query::class);
$packages = [new Package('Behat'), new Package('PhpSpec')];
$templating = $this->prophesize(EngineInterface::class);
$response = new Response('User packages');
!
$container->get('doctrine.orm')->willReturn($doctrine);
$doctrine->getRepository('WebBundle:Package')->willReturn($repository);
$repository->getFilteredQueryBuilder(['maintainer' => $user->getId()])
->willReturn($queryBuilder);
$queryBuilder->orderBy('p.name')->shouldBeCalled();
$queryBuilder->getQuery()->willReturn($query);
$query->execute()->willReturn($packages);
$templating->renderResponse(
'WebBundle:User:packages.html.twig', ['packages' => $packages], null)
->willReturn($response);
!
$controller = new UserController();
$controller->setContainer($container);
$controllerResult = $controller->maintainsPackagesAction($request, $user);
!
$this->assertEquals($response, $controllerResult);
}
“Simple” Symfony2 controller test
public function testShowMaintainedPackages() {
$request = new Request();
$user = new User('everzet');
$container = $this->prophesize(ContainerInterface::class);
$doctrine = $this->prophesize(EntityManager::class);
$repository = $this->prophesize(PackageRepository::class);
$queryBuilder = $this->prophesize(QueryBuilder::class);
$query = $this->prophesize(Query::class);
$packages = [new Package('Behat'), new Package('PhpSpec')];
$templating = $this->prophesize(EngineInterface::class);
$response = new Response('User packages');
!
$container->get('doctrine.orm')->willReturn($doctrine);
$doctrine->getRepository('WebBundle:Package')->willReturn($repository);
$repository->getFilteredQueryBuilder(['maintainer' => $user->getId()])
->willReturn($queryBuilder);
$queryBuilder->orderBy('p.name')->shouldBeCalled();
$queryBuilder->getQuery()->willReturn($query);
$query->execute()->willReturn($packages);
$templating->renderResponse(
'WebBundle:User:packages.html.twig', ['packages' => $packages], null)
->willReturn($response);
!
$controller = new UserController();
$controller->setContainer($container);
$controllerResult = $controller->maintainsPackagesAction($request, $user);
!
$this->assertEquals($response, $controllerResult);
}
Single
Responsibility
Principle
Simpler Symfony2 controller simple test
public function testShowMaintainedPackages() {
$user = new User('everzet');
$repository = $this->prophesize(PackageRepository::class);
$templating = $this->prophesize(EngineInterface::class);
$packages = [new Package('Behat'), new Package('PhpSpec')];
$response = new Response('User packages');
!
$repository->getMaintainedPackagesOrderedByName($user)->willReturn($packages);
$templating->renderResponse(
'WebBundle:User:packages.html.twig', ['packages' => $packages], null)
->willReturn($response);
!
$controller = new UserController($repository->reveal(), $templating->reveal());
$controllerResult = $controller->maintainsPackagesAction($user);
!
$this->assertEquals($response, $controllerResult);
}
Simpler Symfony2 controller simple test
public function testShowMaintainedPackages() {
$user = new User('everzet');
$repository = $this->prophesize(PackageRepository::class);
$templating = $this->prophesize(EngineInterface::class);
$packages = [new Package('Behat'), new Package('PhpSpec')];
$response = new Response('User packages');
!
$repository->getMaintainedPackagesOrderedByName($user)->willReturn($packages);
$templating->renderResponse(
'WebBundle:User:packages.html.twig', ['packages' => $packages], null)
->willReturn($response);
!
$controller = new UserController($repository->reveal(), $templating->reveal());
$controllerResult = $controller->maintainsPackagesAction($user);
!
$this->assertEquals($response, $controllerResult);
}
Simpler Symfony2 controller
public function maintainsPackagesAction(User $user) {
$packages = $this->repo->getMaintainedPackagesOrderedByName($user);
!
return $this->tpl->renderResponse('WebBundle:User:packages.html.twig', [
'packages' => $packages
]);
}
The 2nd case:
basket checkout
Basket checkout
class Basket {
// ...
!
public function checkout(OrderProcessor $processor) {
$totalPrice = new Price::free();
foreach ($this->getItems() as $item) {
$totalPrice = $totalPrice->add($item->getPrice());
$processor->addItem($item);
}
!
$payment = new CashPayment::fromPrice($totalPrice);
$processor->setPayment($payment);
 
$processor->pay();
}
}
Basket checkout test
public function testCheckout() {
$items = [new Item(Price::fromInt(10), Price::fromInt(5)];
$processor = $this->prophesize(OrderProcessor::class);
 
$basket = new Basket($items);
$basket->checkout($processor->reveal());
 
$processor->addItem($items[0])->shouldHaveBeenCalled();
$processor->addItem($items[1])->shouldHaveBeenCalled();
$processor->setPayment(Argument::which('getPrice', 15))->shouldHaveBeenCalled();
$processor->pay()->shouldHaveBeenCalled();
}
Basket checkout test two payments
public function testCheckoutWithCash() {
$items = [new Item(Price::fromInt(10), Price::fromInt(5)];
$processor = $this->prophesize(OrderProcessor::class);
 
$basket = new Basket($items, $credit = false);
$basket->checkout($processor->reveal());
 
$processor->addItem($items[0])->shouldHaveBeenCalled();
$processor->addItem($items[1])->shouldHaveBeenCalled();
$processor->setPayment(Argument::allOf(
Argument::type(CashPayment::class), Argument::which('getPrice', 15)
))->shouldHaveBeenCalled();
$processor->pay()->shouldHaveBeenCalled();
}
 
public function testCheckoutWithCreditCard() {
$items = [new Item(Price::fromInt(10), Price::fromInt(5)];
$processor = $this->prophesize(OrderProcessor::class);
 
$basket = new Basket($items, $credit = true);
$basket->checkout($processor->reveal());
 
$processor->addItem($items[0])->shouldHaveBeenCalled();
$processor->addItem($items[1])->shouldHaveBeenCalled();
$processor->setPayment(Argument::allOf(
Argument::type(CreditPayment::class), Argument::which('getPrice', 15)
))->shouldHaveBeenCalled();
$processor->pay()->shouldHaveBeenCalled();
}
Basket checkout test two payments
public function testCheckoutWithCash() {
$items = [new Item(Price::fromInt(10), Price::fromInt(5)];
$processor = $this->prophesize(OrderProcessor::class);
 
$basket = new Basket($items, $credit = false);
$basket->checkout($processor->reveal());
 
$processor->addItem($items[0])->shouldHaveBeenCalled();
$processor->addItem($items[1])->shouldHaveBeenCalled();
$processor->setPayment(Argument::allOf(
Argument::type(CashPayment::class), Argument::which('getPrice', 15)
))->shouldHaveBeenCalled();
$processor->pay()->shouldHaveBeenCalled();
}
 
public function testCheckoutWithCreditCard() {
$items = [new Item(Price::fromInt(10), Price::fromInt(5)];
$processor = $this->prophesize(OrderProcessor::class);
 
$basket = new Basket($items, $credit = true);
$basket->checkout($processor->reveal());
 
$processor->addItem($items[0])->shouldHaveBeenCalled();
$processor->addItem($items[1])->shouldHaveBeenCalled();
$processor->setPayment(Argument::allOf(
Argument::type(CreditPayment::class), Argument::which('getPrice', 15)
))->shouldHaveBeenCalled();
$processor->pay()->shouldHaveBeenCalled();
}
Basket checkout duplication in test
public function testCheckoutWithCash() {
$items = [new Item(Price::fromInt(10), Price::fromInt(5)];
$processor = $this->prophesize(OrderProcessor::class);
 
$basket = new Basket($items, $credit = false);
$basket->checkout($processor->reveal());
 
$processor->addItem($items[0])->shouldHaveBeenCalled();
$processor->addItem($items[1])->shouldHaveBeenCalled();
$processor->setPayment(Argument::allOf(
Argument::type(CashPayment::class), Argument::which('getPrice', 15)
))->shouldHaveBeenCalled();
$processor->pay()->shouldHaveBeenCalled();
}
 
public function testCheckoutWithCreditCard() {
$items = [new Item(Price::fromInt(10), Price::fromInt(5)];
$processor = $this->prophesize(OrderProcessor::class);
 
$basket = new Basket($items, $credit = true);
$basket->checkout($processor->reveal());
 
$processor->addItem($items[0])->shouldHaveBeenCalled();
$processor->addItem($items[1])->shouldHaveBeenCalled();
$processor->setPayment(Argument::allOf(
Argument::type(CreditPayment::class), Argument::which('getPrice', 15)
))->shouldHaveBeenCalled();
$processor->pay()->shouldHaveBeenCalled();
}
Open
Closed
Principle
Basket checkout test simplification
public function testCheckoutWithPaymentMethod() {
$items = [new Item(Price::fromInt(10), Price::fromInt(5)];
$processor = $this->prophesize(OrderProcessor::class);
$paymentMethod = $this->prophesize(PaymentMethod::class);
$payment = $this->prophesize(Payment::class);
 
$paymentMethod->acceptPayment(Price::fromInt(15))->willReturn($payment);
 
$basket = new Basket($items);
$basket->checkout($processor->reveal(), $paymentMethod->reveal());
 
$processor->addItem($items[0])->shouldHaveBeenCalled();
$processor->addItem($items[1])->shouldHaveBeenCalled();
$processor->setPayment($payment)->shouldHaveBeenCalled();
$processor->pay()->shouldHaveBeenCalled();
}
Basket checkout test simplification
public function testCheckoutWithPaymentMethod() {
$items = [new Item(Price::fromInt(10), Price::fromInt(5)];
$processor = $this->prophesize(OrderProcessor::class);
$paymentMethod = $this->prophesize(PaymentMethod::class);
$payment = $this->prophesize(Payment::class);
 
$paymentMethod->acceptPayment(Price::fromInt(15))->willReturn($payment);
 
$basket = new Basket($items);
$basket->checkout($processor->reveal(), $paymentMethod->reveal());
 
$processor->addItem($items[0])->shouldHaveBeenCalled();
$processor->addItem($items[1])->shouldHaveBeenCalled();
$processor->setPayment($payment)->shouldHaveBeenCalled();
$processor->pay()->shouldHaveBeenCalled();
}
Final basket checkout
class Basket {
// ...
 
public function checkout(OrderProcessor $processor, PaymentMethod $method) {
$totalPrice = new Price::free();
foreach ($this->getItems() as $item) {
$totalPrice = $totalPrice->add($item->getPrice());
$processor->addItem($item);
}
 
$payment = $method->acceptPayment($totalPrice);
$processor->setPayment($payment);
 
$processor->pay();
}
}
Final basket checkout
class Basket {
// ...
 
public function checkout(OrderProcessor $processor, PaymentMethod $method) {
$totalPrice = new Price::free();
foreach ($this->getItems() as $item) {
$totalPrice = $totalPrice->add($item->getPrice());
$processor->addItem($item);
}
 
$payment = $method->acceptPayment($totalPrice);
$processor->setPayment($payment);
 
$processor->pay();
}
}
The 3rd case:
browser emulation
Browser
class Browser {
public function __construct(BrowserDriver $driver) {
$this->driver = $driver;
}
 
public function goto($url) {
$this->driver->boot();
$this->driver->visit($url);
}
}
Browser drivers
interface BrowserDriver {
public function boot();
public function visit($url);
}
!
interface HeadlessBrowserDriver extends BrowserDriver {}
!
class SeleniumDriver implements BrowserDriver {
public function boot() {
$this->selenium->startBrowser($this->browser);
}
!
public function visit($url) {
$this->selenium->visitUrl($url);
}
}
!
class GuzzleDriver implements HeadlessBrowserDriver {
public function boot() {}
!
public function visit($url) {
$this->guzzle->openUrl($url);
}
}
Headless driver test
public function testVisitingProvidedUrl() {
$url = 'http://en.wikipedia.org';
$driver = $this->prophesize(HeadlessBrowserDriver::class);
!
$driver->visit($url)->shouldBeCalled();
!
$browser = new Browser($driver->reveal());
$browser->goto($url);
!
$this->getProphecy()->checkPredictions();
}
Failing headless driver test
public function testVisitingProvidedUrl() {
$url = 'http://en.wikipedia.org';
$driver = $this->prophesize(HeadlessBrowserDriver::class);
!
$driver->visit($url)->shouldBeCalled();
!
$browser = new Browser($driver->reveal());
$browser->goto($url);
!
$this->getProphecy()->checkPredictions();
}
Refused Bequest
Headless driver implementation
class GuzzleDriver implements HeadlessBrowserDriver {
!
public function boot() {}
!
public function visit($url) {
$this->guzzle->openUrl($url);
}
}
Headless driver simple behaviour
class GuzzleDriver implements HeadlessBrowserDriver {
!
public function boot() {}
!
public function visit($url) {
$this->guzzle->openUrl($url);
}
}
Headless driver that knows about booting
class GuzzleDriver implements HeadlessBrowserDriver {
!
public function boot() {
$this->allowDoActions = true;
}
 
public function visit($url) {
if ($this->allowDoActions)
$this->guzzle->openUrl($url);
}
}
Liskov
Substitution
Principle
Adapter layer between BrowserDriver and HeadlessBrowserDriver
interface HeadlessBrowserDriver {
public function visit($url);
}
!
class GuzzleDriver implements HeadlessBrowserDriver {
public function visit($url) {
$this->guzzle->openUrl($url);
}
}
!
final class HeadlessBrowserAdapter implements BrowserDriver {
private $headlessDriver, $allowDoAction = false;
!
public function __construct(HeadlessBrowserDriver $headlessDriver) {
$this->headlessDriver = $headlessDriver;
}
!
public function boot() {
$this->allowDoActions = true;
}
!
public function visit($url) {
if ($this->allowDoActions)
$this->headlessDriver->visit($url);
}
}
Dirty adapter layer between BrowserDriver and HeadlessBrowserDriver
interface HeadlessBrowserDriver {
public function visit($url);
}
!
class GuzzleDriver implements HeadlessBrowserDriver {
public function visit($url) {
$this->guzzle->openUrl($url);
}
}
!
final class HeadlessBrowserAdapter implements BrowserDriver {
private $headlessDriver, $allowDoAction = false;
!
public function __construct(HeadlessBrowserDriver $headlessDriver) {
$this->headlessDriver = $headlessDriver;
}
!
public function boot() {
$this->allowDoActions = true;
}
!
public function visit($url) {
if ($this->allowDoActions)
$this->headlessDriver->visit($url);
}
}
Single adapter layer between BrowserDriver and HeadlessBrowserDriver
interface HeadlessBrowserDriver {
public function visit($url);
}
!
class GuzzleDriver implements HeadlessBrowserDriver {
public function visit($url) {
$this->guzzle->openUrl($url);
}
}
!
final class HeadlessBrowserAdapter implements BrowserDriver {
private $headlessDriver, $allowDoAction = false;
!
public function __construct(HeadlessBrowserDriver $headlessDriver) {
$this->headlessDriver = $headlessDriver;
}
!
public function boot() {
$this->allowDoActions = true;
}
!
public function visit($url) {
if ($this->allowDoActions)
$this->headlessDriver->visit($url);
}
}
The 4th case:
ATM screen
ATM messenger interface
interface Messenger {
public function askForCard();
public function askForPin();
public function askForAccount();
public function askForAmount();
public function tellNoMoney();
public function tellMachineEmpty();
}
City ATM login test
public function testAtmAsksForCardAndPinDuringLogin() {
$messenger = $this->prophesize(Messenger::class);
!
$messenger->askForCard()->shouldBeCalled();
$messenger->askForPin()->shouldBeCalled();
!
$atm = new CityAtm($messenger->reveal());
$atm->login();
!
$this->getProphet()->checkPredictions();
}
City ATM login test
public function testAtmAsksForCardAndPinDuringLogin() {
$messenger = $this->prophesize(Messenger::class);
!
$messenger->askForCard()->shouldBeCalled();
$messenger->askForPin()->shouldBeCalled();
!
$atm = new CityAtm($messenger->reveal());
$atm->login();
!
$this->getProphet()->checkPredictions();
}
Interface
Segregation
Principle
City ATM login test
public function testAtmAsksForCardAndPinDuringLogin() {
$messenger = $this->prophesize(LoginMessenger::class);
!
$messenger->askForCard()->shouldBeCalled();
$messenger->askForPin()->shouldBeCalled();
!
$atm = new CityAtm($messenger->reveal());
$atm->login();
!
$this->getProphet()->checkPredictions();
}
ATM messenger interface(s)
interface LoginMessenger {
public function askForCard();
public function askForPin();
}
!
interface InputMessenger {
public function askForAccount();
public function askForAmount();
}
!
interface WithdrawalMessenger {
public function tellNoMoney();
public function tellMachineEmpty();
}
!
interface Messenger extends LoginMessenger,
InputMessenger,
WithdrawalMessenger
The 5th case:
entity repository
Doctrine entity repository
class JobRepository extends EntityRepository {
public function findJobByName($name) {
return $this->findOneBy(['name' => $name]);
}
}
Doctrine entity repository test
public function testFindingJobsByName() {
$em = $this->prophesize('EntityManager');
$cmd = $this->prophesize('ClassMetadata');
$uow = $this->prophesize('UnitOfWork');
$ep = $this->prophesize('EntityPersister');
$job = Job::fromName('engineer');
!
$em->getUnitOfWork()->willReturn($uow);
$uow->getEntityPersister(Argument::any())->willReturn($ep);
$ep->load(['name' => 'engineer'], null, null, [], null, 1, null)
->willReturn($job);
!
$repo = new JobRepository($em->reveal(), $cmd->reveal());
$actualJob = $repo->findJobByName('engineer');
!
$this->assertSame($job, $actualJob);
}
Doctrine entity repository test
public function testFindingJobsByName() {
$em = $this->prophesize('EntityManager');
$cmd = $this->prophesize('ClassMetadata');
$uow = $this->prophesize('UnitOfWork');
$ep = $this->prophesize('EntityPersister');
$job = Job::fromName('engineer');
!
$em->getUnitOfWork()->willReturn($uow);
$uow->getEntityPersister(Argument::any())->willReturn($ep);
$ep->load(['name' => 'engineer'], null, null, [], null, 1, null)
->willReturn($job);
!
$repo = new JobRepository($em->reveal(), $cmd->reveal());
$actualJob = $repo->findJobByName('engineer');
!
$this->assertSame($job, $actualJob);
}
Do not mock things
you do not own
Doctrine entity repository test
public function testFindingJobsByName() {
$em = $this->prophesize('EntityManager');
$cmd = $this->prophesize('ClassMetadata');
$uow = $this->prophesize('UnitOfWork');
$ep = $this->prophesize('EntityPersister');
$job = Job::fromName('engineer');
!
$em->getUnitOfWork()->willReturn($uow);
$uow->getEntityPersister(Argument::any())->willReturn($ep);
$ep->load(['name' => 'engineer'], null, null, [], null, 1, null)
->willReturn($job);
!
$repo = new JobRepository($em->reveal(), $cmd->reveal());
$actualJob = $repo->findJobByName('engineer');
!
$this->assertSame($job, $actualJob);
}
Dependency
Inversion
Principle
Job repository & Doctrine implementation of it
interface	
  JobRepository	
  {	
  
	
  	
  	
  	
  public	
  function	
  findJobByName($name);	
  
}	
  
!
class	
  DoctrineJobRepository	
  extends	
  EntityRepository	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  implements	
  JobRepository	
  {	
  
!
	
  	
  	
  	
  public	
  function	
  findJobByName($name)	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  return	
  $this-­‐>findOneBy(['name'	
  =>	
  $name]);	
  
	
  	
  	
  	
  }	
  
}
Job repository & Doctrine implementation of it
interface	
  JobRepository	
  {	
  
	
  	
  	
  	
  public	
  function	
  findJobByName($name);	
  
}	
  
!
class	
  DoctrineJobRepository	
  extends	
  EntityRepository	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  implements	
  JobRepository	
  {	
  
!
	
  	
  	
  	
  public	
  function	
  findJobByName($name)	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  return	
  $this-­‐>findOneBy(['name'	
  =>	
  $name]);	
  
	
  	
  	
  	
  }	
  
}
Job repository & Doctrine implementation of it
interface	
  JobRepository	
  {	
  
	
  	
  	
  	
  public	
  function	
  findJobByName($name);	
  
}	
  
!
class	
  DoctrineJobRepository	
  extends	
  EntityRepository	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  implements	
  JobRepository	
  {	
  
!
	
  	
  	
  	
  public	
  function	
  findJobByName($name)	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  return	
  $this-­‐>findOneBy(['name'	
  =>	
  $name]);	
  
	
  	
  	
  	
  }	
  
}
Recap:
Recap:
1. State-focused TDD is not the only way to TDD
Recap:
1. State-focused TDD is not the only way to TDD

2. Messaging is far more important concept of OOP than
the state
Recap:
1. State-focused TDD is not the only way to TDD

2. Messaging is far more important concept of OOP than
the state

3. By focusing on messaging, you expose messaging
problems
Recap:
1. State-focused TDD is not the only way to TDD

2. Messaging is far more important concept of OOP than
the state

3. By focusing on messaging, you expose messaging
problems

4. By exposing messaging problems, you could discover
most of the SOLID principles violation before they
happen
Recap:
1. State-focused TDD is not the only way to TDD

2. Messaging is far more important concept of OOP than the
state

3. By focusing on messaging, you expose messaging
problems

4. By exposing messaging problems, you could discover
most of the SOLID principles violation before they happen

5. Prophecy is awesome
6. Messages define
objects behaviour
Thank you!

Weitere ähnliche Inhalte

Was ist angesagt?

Decoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDDDecoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDDAleix Vergés
 
Symfony CoP: Form component
Symfony CoP: Form componentSymfony CoP: Form component
Symfony CoP: Form componentSamuel ROZE
 
Design Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et PimpleDesign Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et PimpleHugo Hamon
 
Symfony & Javascript. Combining the best of two worlds
Symfony & Javascript. Combining the best of two worldsSymfony & Javascript. Combining the best of two worlds
Symfony & Javascript. Combining the best of two worldsIgnacio Martín
 
Min-Maxing Software Costs - Laracon EU 2015
Min-Maxing Software Costs - Laracon EU 2015Min-Maxing Software Costs - Laracon EU 2015
Min-Maxing Software Costs - Laracon EU 2015Konstantin Kudryashov
 
New Symfony Tips & Tricks (SymfonyCon Paris 2015)
New Symfony Tips & Tricks (SymfonyCon Paris 2015)New Symfony Tips & Tricks (SymfonyCon Paris 2015)
New Symfony Tips & Tricks (SymfonyCon Paris 2015)Javier Eguiluz
 
Introduction to CQRS and Event Sourcing
Introduction to CQRS and Event SourcingIntroduction to CQRS and Event Sourcing
Introduction to CQRS and Event SourcingSamuel ROZE
 
How I started to love design patterns
How I started to love design patternsHow I started to love design patterns
How I started to love design patternsSamuel ROZE
 
Doctrine fixtures
Doctrine fixturesDoctrine fixtures
Doctrine fixturesBill Chang
 
Crafting beautiful software
Crafting beautiful softwareCrafting beautiful software
Crafting beautiful softwareJorn Oomen
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For BeginnersJonathan Wage
 
Silex meets SOAP & REST
Silex meets SOAP & RESTSilex meets SOAP & REST
Silex meets SOAP & RESTHugo Hamon
 
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you needDutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you needKacper Gunia
 
PHP 5.3 and Lithium: the most rad php framework
PHP 5.3 and Lithium: the most rad php frameworkPHP 5.3 and Lithium: the most rad php framework
PHP 5.3 and Lithium: the most rad php frameworkG Woo
 
The IoC Hydra - Dutch PHP Conference 2016
The IoC Hydra - Dutch PHP Conference 2016The IoC Hydra - Dutch PHP Conference 2016
The IoC Hydra - Dutch PHP Conference 2016Kacper Gunia
 
Database Design Patterns
Database Design PatternsDatabase Design Patterns
Database Design PatternsHugo Hamon
 
The History of PHPersistence
The History of PHPersistenceThe History of PHPersistence
The History of PHPersistenceHugo Hamon
 
The Zen of Lithium
The Zen of LithiumThe Zen of Lithium
The Zen of LithiumNate Abele
 
Speed up your developments with Symfony2
Speed up your developments with Symfony2Speed up your developments with Symfony2
Speed up your developments with Symfony2Hugo Hamon
 

Was ist angesagt? (20)

Decoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDDDecoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDD
 
Symfony CoP: Form component
Symfony CoP: Form componentSymfony CoP: Form component
Symfony CoP: Form component
 
Design Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et PimpleDesign Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et Pimple
 
Symfony & Javascript. Combining the best of two worlds
Symfony & Javascript. Combining the best of two worldsSymfony & Javascript. Combining the best of two worlds
Symfony & Javascript. Combining the best of two worlds
 
Min-Maxing Software Costs - Laracon EU 2015
Min-Maxing Software Costs - Laracon EU 2015Min-Maxing Software Costs - Laracon EU 2015
Min-Maxing Software Costs - Laracon EU 2015
 
New Symfony Tips & Tricks (SymfonyCon Paris 2015)
New Symfony Tips & Tricks (SymfonyCon Paris 2015)New Symfony Tips & Tricks (SymfonyCon Paris 2015)
New Symfony Tips & Tricks (SymfonyCon Paris 2015)
 
Introduction to CQRS and Event Sourcing
Introduction to CQRS and Event SourcingIntroduction to CQRS and Event Sourcing
Introduction to CQRS and Event Sourcing
 
How I started to love design patterns
How I started to love design patternsHow I started to love design patterns
How I started to love design patterns
 
Doctrine fixtures
Doctrine fixturesDoctrine fixtures
Doctrine fixtures
 
The IoC Hydra
The IoC HydraThe IoC Hydra
The IoC Hydra
 
Crafting beautiful software
Crafting beautiful softwareCrafting beautiful software
Crafting beautiful software
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
 
Silex meets SOAP & REST
Silex meets SOAP & RESTSilex meets SOAP & REST
Silex meets SOAP & REST
 
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you needDutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
 
PHP 5.3 and Lithium: the most rad php framework
PHP 5.3 and Lithium: the most rad php frameworkPHP 5.3 and Lithium: the most rad php framework
PHP 5.3 and Lithium: the most rad php framework
 
The IoC Hydra - Dutch PHP Conference 2016
The IoC Hydra - Dutch PHP Conference 2016The IoC Hydra - Dutch PHP Conference 2016
The IoC Hydra - Dutch PHP Conference 2016
 
Database Design Patterns
Database Design PatternsDatabase Design Patterns
Database Design Patterns
 
The History of PHPersistence
The History of PHPersistenceThe History of PHPersistence
The History of PHPersistence
 
The Zen of Lithium
The Zen of LithiumThe Zen of Lithium
The Zen of Lithium
 
Speed up your developments with Symfony2
Speed up your developments with Symfony2Speed up your developments with Symfony2
Speed up your developments with Symfony2
 

Andere mochten auch

Get Soaked - An In Depth Look At PHP Streams
Get Soaked - An In Depth Look At PHP StreamsGet Soaked - An In Depth Look At PHP Streams
Get Soaked - An In Depth Look At PHP StreamsDavey Shafik
 
Techniques d'accélération des pages web
Techniques d'accélération des pages webTechniques d'accélération des pages web
Techniques d'accélération des pages webJean-Pierre Vincent
 
Automation using-phing
Automation using-phingAutomation using-phing
Automation using-phingRajat Pandit
 
Electrify your code with PHP Generators
Electrify your code with PHP GeneratorsElectrify your code with PHP Generators
Electrify your code with PHP GeneratorsMark Baker
 
The quest for global design principles (SymfonyLive Berlin 2015)
The quest for global design principles (SymfonyLive Berlin 2015)The quest for global design principles (SymfonyLive Berlin 2015)
The quest for global design principles (SymfonyLive Berlin 2015)Matthias Noback
 
Top tips my_sql_performance
Top tips my_sql_performanceTop tips my_sql_performance
Top tips my_sql_performanceafup Paris
 
Why elasticsearch rocks!
Why elasticsearch rocks!Why elasticsearch rocks!
Why elasticsearch rocks!tlrx
 
Understanding Craftsmanship SwanseaCon2015
Understanding Craftsmanship SwanseaCon2015Understanding Craftsmanship SwanseaCon2015
Understanding Craftsmanship SwanseaCon2015Marcello Duarte
 
Si le tdd est mort alors pratiquons une autopsie mix-it 2015
Si le tdd est mort alors pratiquons une autopsie mix-it 2015Si le tdd est mort alors pratiquons une autopsie mix-it 2015
Si le tdd est mort alors pratiquons une autopsie mix-it 2015Bruno Boucard
 
Writing infinite scalability web applications with PHP and PostgreSQL
Writing infinite scalability web applications with PHP and PostgreSQLWriting infinite scalability web applications with PHP and PostgreSQL
Writing infinite scalability web applications with PHP and PostgreSQLGabriele Bartolini
 
L'ABC du BDD (Behavior Driven Development)
L'ABC du BDD (Behavior Driven Development)L'ABC du BDD (Behavior Driven Development)
L'ABC du BDD (Behavior Driven Development)Arnauld Loyer
 
Performance serveur et apache
Performance serveur et apachePerformance serveur et apache
Performance serveur et apacheafup Paris
 
TDD with PhpSpec - Lone Star PHP 2016
TDD with PhpSpec - Lone Star PHP 2016TDD with PhpSpec - Lone Star PHP 2016
TDD with PhpSpec - Lone Star PHP 2016CiaranMcNulty
 
The Wonderful World of Symfony Components
The Wonderful World of Symfony ComponentsThe Wonderful World of Symfony Components
The Wonderful World of Symfony ComponentsRyan Weaver
 

Andere mochten auch (20)

Elastic Searching With PHP
Elastic Searching With PHPElastic Searching With PHP
Elastic Searching With PHP
 
Diving deep into twig
Diving deep into twigDiving deep into twig
Diving deep into twig
 
Get Soaked - An In Depth Look At PHP Streams
Get Soaked - An In Depth Look At PHP StreamsGet Soaked - An In Depth Look At PHP Streams
Get Soaked - An In Depth Look At PHP Streams
 
Techniques d'accélération des pages web
Techniques d'accélération des pages webTechniques d'accélération des pages web
Techniques d'accélération des pages web
 
Automation using-phing
Automation using-phingAutomation using-phing
Automation using-phing
 
PHP5.5 is Here
PHP5.5 is HerePHP5.5 is Here
PHP5.5 is Here
 
Electrify your code with PHP Generators
Electrify your code with PHP GeneratorsElectrify your code with PHP Generators
Electrify your code with PHP Generators
 
The quest for global design principles (SymfonyLive Berlin 2015)
The quest for global design principles (SymfonyLive Berlin 2015)The quest for global design principles (SymfonyLive Berlin 2015)
The quest for global design principles (SymfonyLive Berlin 2015)
 
Top tips my_sql_performance
Top tips my_sql_performanceTop tips my_sql_performance
Top tips my_sql_performance
 
Mocking Demystified
Mocking DemystifiedMocking Demystified
Mocking Demystified
 
Why elasticsearch rocks!
Why elasticsearch rocks!Why elasticsearch rocks!
Why elasticsearch rocks!
 
Understanding Craftsmanship SwanseaCon2015
Understanding Craftsmanship SwanseaCon2015Understanding Craftsmanship SwanseaCon2015
Understanding Craftsmanship SwanseaCon2015
 
Si le tdd est mort alors pratiquons une autopsie mix-it 2015
Si le tdd est mort alors pratiquons une autopsie mix-it 2015Si le tdd est mort alors pratiquons une autopsie mix-it 2015
Si le tdd est mort alors pratiquons une autopsie mix-it 2015
 
Writing infinite scalability web applications with PHP and PostgreSQL
Writing infinite scalability web applications with PHP and PostgreSQLWriting infinite scalability web applications with PHP and PostgreSQL
Writing infinite scalability web applications with PHP and PostgreSQL
 
L'ABC du BDD (Behavior Driven Development)
L'ABC du BDD (Behavior Driven Development)L'ABC du BDD (Behavior Driven Development)
L'ABC du BDD (Behavior Driven Development)
 
Performance serveur et apache
Performance serveur et apachePerformance serveur et apache
Performance serveur et apache
 
Behat 3.0 meetup (March)
Behat 3.0 meetup (March)Behat 3.0 meetup (March)
Behat 3.0 meetup (March)
 
Caching on the Edge
Caching on the EdgeCaching on the Edge
Caching on the Edge
 
TDD with PhpSpec - Lone Star PHP 2016
TDD with PhpSpec - Lone Star PHP 2016TDD with PhpSpec - Lone Star PHP 2016
TDD with PhpSpec - Lone Star PHP 2016
 
The Wonderful World of Symfony Components
The Wonderful World of Symfony ComponentsThe Wonderful World of Symfony Components
The Wonderful World of Symfony Components
 

Ähnlich wie Design how objects communicate through mocking

Advanced php testing in action
Advanced php testing in actionAdvanced php testing in action
Advanced php testing in actionJace Ju
 
Mocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnitMocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnitmfrost503
 
Unit testing with zend framework tek11
Unit testing with zend framework tek11Unit testing with zend framework tek11
Unit testing with zend framework tek11Michelangelo van Dam
 
Unit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBeneluxUnit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBeneluxMichelangelo van Dam
 
PHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4DevelopersPHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4DevelopersKacper Gunia
 
Mocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnitMocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnitmfrost503
 
Как получить чёрный пояс по WordPress?
Как получить чёрный пояс по WordPress?Как получить чёрный пояс по WordPress?
Как получить чёрный пояс по WordPress?Yevhen Kotelnytskyi
 
Your code sucks, let's fix it - DPC UnCon
Your code sucks, let's fix it - DPC UnConYour code sucks, let's fix it - DPC UnCon
Your code sucks, let's fix it - DPC UnConRafael Dohms
 
You code sucks, let's fix it
You code sucks, let's fix itYou code sucks, let's fix it
You code sucks, let's fix itRafael Dohms
 
EPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur PurnamaEPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur PurnamaEnterprise PHP Center
 
Как получить чёрный пояс по WordPress? v2.0
Как получить чёрный пояс по WordPress? v2.0Как получить чёрный пояс по WordPress? v2.0
Как получить чёрный пояс по WordPress? v2.0Yevhen Kotelnytskyi
 
WordPress as an application framework
WordPress as an application frameworkWordPress as an application framework
WordPress as an application frameworkDustin Filippini
 
Quality assurance for php projects with PHPStorm
Quality assurance for php projects with PHPStormQuality assurance for php projects with PHPStorm
Quality assurance for php projects with PHPStormMichelangelo van Dam
 
Command-Oriented Architecture
Command-Oriented ArchitectureCommand-Oriented Architecture
Command-Oriented ArchitectureLuiz Messias
 
Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8Michelangelo van Dam
 
Load Testing with PHP and RedLine13
Load Testing with PHP and RedLine13Load Testing with PHP and RedLine13
Load Testing with PHP and RedLine13Jason Lotito
 

Ähnlich wie Design how objects communicate through mocking (20)

Advanced php testing in action
Advanced php testing in actionAdvanced php testing in action
Advanced php testing in action
 
Unit testing zend framework apps
Unit testing zend framework appsUnit testing zend framework apps
Unit testing zend framework apps
 
Mocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnitMocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnit
 
Unit testing with zend framework tek11
Unit testing with zend framework tek11Unit testing with zend framework tek11
Unit testing with zend framework tek11
 
Unit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBeneluxUnit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBenelux
 
PHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4DevelopersPHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4Developers
 
Mocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnitMocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnit
 
Как получить чёрный пояс по WordPress?
Как получить чёрный пояс по WordPress?Как получить чёрный пояс по WordPress?
Как получить чёрный пояс по WordPress?
 
Your code sucks, let's fix it - DPC UnCon
Your code sucks, let's fix it - DPC UnConYour code sucks, let's fix it - DPC UnCon
Your code sucks, let's fix it - DPC UnCon
 
You code sucks, let's fix it
You code sucks, let's fix itYou code sucks, let's fix it
You code sucks, let's fix it
 
EPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur PurnamaEPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur Purnama
 
Как получить чёрный пояс по WordPress? v2.0
Как получить чёрный пояс по WordPress? v2.0Как получить чёрный пояс по WordPress? v2.0
Как получить чёрный пояс по WordPress? v2.0
 
Zend framework service
Zend framework serviceZend framework service
Zend framework service
 
Zend framework service
Zend framework serviceZend framework service
Zend framework service
 
PHP Unit Testing
PHP Unit TestingPHP Unit Testing
PHP Unit Testing
 
WordPress as an application framework
WordPress as an application frameworkWordPress as an application framework
WordPress as an application framework
 
Quality assurance for php projects with PHPStorm
Quality assurance for php projects with PHPStormQuality assurance for php projects with PHPStorm
Quality assurance for php projects with PHPStorm
 
Command-Oriented Architecture
Command-Oriented ArchitectureCommand-Oriented Architecture
Command-Oriented Architecture
 
Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8
 
Load Testing with PHP and RedLine13
Load Testing with PHP and RedLine13Load Testing with PHP and RedLine13
Load Testing with PHP and RedLine13
 

Mehr von Konstantin Kudryashov

Being effective with legacy projects
Being effective with legacy projectsBeing effective with legacy projects
Being effective with legacy projectsKonstantin Kudryashov
 
Bridging The Communication Gap, Fast
Bridging The Communication Gap, Fast Bridging The Communication Gap, Fast
Bridging The Communication Gap, Fast Konstantin Kudryashov
 
Moving away from legacy code (AgileCymru)
Moving away from legacy code  (AgileCymru)Moving away from legacy code  (AgileCymru)
Moving away from legacy code (AgileCymru)Konstantin Kudryashov
 
Moving away from legacy code with BDD
Moving away from legacy code with BDDMoving away from legacy code with BDD
Moving away from legacy code with BDDKonstantin Kudryashov
 
Enabling agile devliery through enabling BDD in PHP projects
Enabling agile devliery through enabling BDD in PHP projectsEnabling agile devliery through enabling BDD in PHP projects
Enabling agile devliery through enabling BDD in PHP projectsKonstantin Kudryashov
 
Moving away from legacy code with BDD
Moving away from legacy code with BDDMoving away from legacy code with BDD
Moving away from legacy code with BDDKonstantin Kudryashov
 
LESS, SASS, HAML: 4 буквы, изменившие frontend development
LESS, SASS, HAML: 4 буквы, изменившие frontend developmentLESS, SASS, HAML: 4 буквы, изменившие frontend development
LESS, SASS, HAML: 4 буквы, изменившие frontend developmentKonstantin Kudryashov
 
Автоматизируем деплоймент проекта с помощью Capistrano
Автоматизируем деплоймент проекта с помощью CapistranoАвтоматизируем деплоймент проекта с помощью Capistrano
Автоматизируем деплоймент проекта с помощью CapistranoKonstantin Kudryashov
 

Mehr von Konstantin Kudryashov (14)

Modern Agile Project Toolbox
Modern Agile Project ToolboxModern Agile Project Toolbox
Modern Agile Project Toolbox
 
Being effective with legacy projects
Being effective with legacy projectsBeing effective with legacy projects
Being effective with legacy projects
 
Modern Project Toolbox
Modern Project ToolboxModern Project Toolbox
Modern Project Toolbox
 
Bridging The Communication Gap, Fast
Bridging The Communication Gap, Fast Bridging The Communication Gap, Fast
Bridging The Communication Gap, Fast
 
Moving away from legacy code (AgileCymru)
Moving away from legacy code  (AgileCymru)Moving away from legacy code  (AgileCymru)
Moving away from legacy code (AgileCymru)
 
Taking back BDD
Taking back BDDTaking back BDD
Taking back BDD
 
Moving away from legacy code with BDD
Moving away from legacy code with BDDMoving away from legacy code with BDD
Moving away from legacy code with BDD
 
Enabling agile devliery through enabling BDD in PHP projects
Enabling agile devliery through enabling BDD in PHP projectsEnabling agile devliery through enabling BDD in PHP projects
Enabling agile devliery through enabling BDD in PHP projects
 
Moving away from legacy code with BDD
Moving away from legacy code with BDDMoving away from legacy code with BDD
Moving away from legacy code with BDD
 
BDD в PHP с Behat и Mink
BDD в PHP с Behat и MinkBDD в PHP с Behat и Mink
BDD в PHP с Behat и Mink
 
BDD in Symfony2
BDD in Symfony2BDD in Symfony2
BDD in Symfony2
 
BDD для PHP проектов
BDD для PHP проектовBDD для PHP проектов
BDD для PHP проектов
 
LESS, SASS, HAML: 4 буквы, изменившие frontend development
LESS, SASS, HAML: 4 буквы, изменившие frontend developmentLESS, SASS, HAML: 4 буквы, изменившие frontend development
LESS, SASS, HAML: 4 буквы, изменившие frontend development
 
Автоматизируем деплоймент проекта с помощью Capistrano
Автоматизируем деплоймент проекта с помощью CapistranoАвтоматизируем деплоймент проекта с помощью Capistrano
Автоматизируем деплоймент проекта с помощью Capistrano
 

Kürzlich hochgeladen

Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...Cizo Technology Services
 
Patterns for automating API delivery. API conference
Patterns for automating API delivery. API conferencePatterns for automating API delivery. API conference
Patterns for automating API delivery. API conferencessuser9e7c64
 
OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full Recording
OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full RecordingOpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full Recording
OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full RecordingShane Coughlan
 
Osi security architecture in network.pptx
Osi security architecture in network.pptxOsi security architecture in network.pptx
Osi security architecture in network.pptxVinzoCenzo
 
Strategies for using alternative queries to mitigate zero results
Strategies for using alternative queries to mitigate zero resultsStrategies for using alternative queries to mitigate zero results
Strategies for using alternative queries to mitigate zero resultsJean Silva
 
Best Angular 17 Classroom & Online training - Naresh IT
Best Angular 17 Classroom & Online training - Naresh ITBest Angular 17 Classroom & Online training - Naresh IT
Best Angular 17 Classroom & Online training - Naresh ITmanoharjgpsolutions
 
Precise and Complete Requirements? An Elusive Goal
Precise and Complete Requirements? An Elusive GoalPrecise and Complete Requirements? An Elusive Goal
Precise and Complete Requirements? An Elusive GoalLionel Briand
 
eSoftTools IMAP Backup Software and migration tools
eSoftTools IMAP Backup Software and migration toolseSoftTools IMAP Backup Software and migration tools
eSoftTools IMAP Backup Software and migration toolsosttopstonverter
 
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...OnePlan Solutions
 
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...confluent
 
VictoriaMetrics Q1 Meet Up '24 - Community & News Update
VictoriaMetrics Q1 Meet Up '24 - Community & News UpdateVictoriaMetrics Q1 Meet Up '24 - Community & News Update
VictoriaMetrics Q1 Meet Up '24 - Community & News UpdateVictoriaMetrics
 
Introduction to Firebase Workshop Slides
Introduction to Firebase Workshop SlidesIntroduction to Firebase Workshop Slides
Introduction to Firebase Workshop Slidesvaideheekore1
 
The Role of IoT and Sensor Technology in Cargo Cloud Solutions.pptx
The Role of IoT and Sensor Technology in Cargo Cloud Solutions.pptxThe Role of IoT and Sensor Technology in Cargo Cloud Solutions.pptx
The Role of IoT and Sensor Technology in Cargo Cloud Solutions.pptxRTS corp
 
OpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full Recording
OpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full RecordingOpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full Recording
OpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full RecordingShane Coughlan
 
UI5ers live - Custom Controls wrapping 3rd-party libs.pptx
UI5ers live - Custom Controls wrapping 3rd-party libs.pptxUI5ers live - Custom Controls wrapping 3rd-party libs.pptx
UI5ers live - Custom Controls wrapping 3rd-party libs.pptxAndreas Kunz
 
Enhancing Supply Chain Visibility with Cargo Cloud Solutions.pdf
Enhancing Supply Chain Visibility with Cargo Cloud Solutions.pdfEnhancing Supply Chain Visibility with Cargo Cloud Solutions.pdf
Enhancing Supply Chain Visibility with Cargo Cloud Solutions.pdfRTS corp
 
JavaLand 2024 - Going serverless with Quarkus GraalVM native images and AWS L...
JavaLand 2024 - Going serverless with Quarkus GraalVM native images and AWS L...JavaLand 2024 - Going serverless with Quarkus GraalVM native images and AWS L...
JavaLand 2024 - Going serverless with Quarkus GraalVM native images and AWS L...Bert Jan Schrijver
 
VK Business Profile - provides IT solutions and Web Development
VK Business Profile - provides IT solutions and Web DevelopmentVK Business Profile - provides IT solutions and Web Development
VK Business Profile - provides IT solutions and Web Developmentvyaparkranti
 
Comparing Linux OS Image Update Models - EOSS 2024.pdf
Comparing Linux OS Image Update Models - EOSS 2024.pdfComparing Linux OS Image Update Models - EOSS 2024.pdf
Comparing Linux OS Image Update Models - EOSS 2024.pdfDrew Moseley
 
Exploring Selenium_Appium Frameworks for Seamless Integration with HeadSpin.pdf
Exploring Selenium_Appium Frameworks for Seamless Integration with HeadSpin.pdfExploring Selenium_Appium Frameworks for Seamless Integration with HeadSpin.pdf
Exploring Selenium_Appium Frameworks for Seamless Integration with HeadSpin.pdfkalichargn70th171
 

Kürzlich hochgeladen (20)

Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
 
Patterns for automating API delivery. API conference
Patterns for automating API delivery. API conferencePatterns for automating API delivery. API conference
Patterns for automating API delivery. API conference
 
OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full Recording
OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full RecordingOpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full Recording
OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full Recording
 
Osi security architecture in network.pptx
Osi security architecture in network.pptxOsi security architecture in network.pptx
Osi security architecture in network.pptx
 
Strategies for using alternative queries to mitigate zero results
Strategies for using alternative queries to mitigate zero resultsStrategies for using alternative queries to mitigate zero results
Strategies for using alternative queries to mitigate zero results
 
Best Angular 17 Classroom & Online training - Naresh IT
Best Angular 17 Classroom & Online training - Naresh ITBest Angular 17 Classroom & Online training - Naresh IT
Best Angular 17 Classroom & Online training - Naresh IT
 
Precise and Complete Requirements? An Elusive Goal
Precise and Complete Requirements? An Elusive GoalPrecise and Complete Requirements? An Elusive Goal
Precise and Complete Requirements? An Elusive Goal
 
eSoftTools IMAP Backup Software and migration tools
eSoftTools IMAP Backup Software and migration toolseSoftTools IMAP Backup Software and migration tools
eSoftTools IMAP Backup Software and migration tools
 
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
 
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
 
VictoriaMetrics Q1 Meet Up '24 - Community & News Update
VictoriaMetrics Q1 Meet Up '24 - Community & News UpdateVictoriaMetrics Q1 Meet Up '24 - Community & News Update
VictoriaMetrics Q1 Meet Up '24 - Community & News Update
 
Introduction to Firebase Workshop Slides
Introduction to Firebase Workshop SlidesIntroduction to Firebase Workshop Slides
Introduction to Firebase Workshop Slides
 
The Role of IoT and Sensor Technology in Cargo Cloud Solutions.pptx
The Role of IoT and Sensor Technology in Cargo Cloud Solutions.pptxThe Role of IoT and Sensor Technology in Cargo Cloud Solutions.pptx
The Role of IoT and Sensor Technology in Cargo Cloud Solutions.pptx
 
OpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full Recording
OpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full RecordingOpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full Recording
OpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full Recording
 
UI5ers live - Custom Controls wrapping 3rd-party libs.pptx
UI5ers live - Custom Controls wrapping 3rd-party libs.pptxUI5ers live - Custom Controls wrapping 3rd-party libs.pptx
UI5ers live - Custom Controls wrapping 3rd-party libs.pptx
 
Enhancing Supply Chain Visibility with Cargo Cloud Solutions.pdf
Enhancing Supply Chain Visibility with Cargo Cloud Solutions.pdfEnhancing Supply Chain Visibility with Cargo Cloud Solutions.pdf
Enhancing Supply Chain Visibility with Cargo Cloud Solutions.pdf
 
JavaLand 2024 - Going serverless with Quarkus GraalVM native images and AWS L...
JavaLand 2024 - Going serverless with Quarkus GraalVM native images and AWS L...JavaLand 2024 - Going serverless with Quarkus GraalVM native images and AWS L...
JavaLand 2024 - Going serverless with Quarkus GraalVM native images and AWS L...
 
VK Business Profile - provides IT solutions and Web Development
VK Business Profile - provides IT solutions and Web DevelopmentVK Business Profile - provides IT solutions and Web Development
VK Business Profile - provides IT solutions and Web Development
 
Comparing Linux OS Image Update Models - EOSS 2024.pdf
Comparing Linux OS Image Update Models - EOSS 2024.pdfComparing Linux OS Image Update Models - EOSS 2024.pdf
Comparing Linux OS Image Update Models - EOSS 2024.pdf
 
Exploring Selenium_Appium Frameworks for Seamless Integration with HeadSpin.pdf
Exploring Selenium_Appium Frameworks for Seamless Integration with HeadSpin.pdfExploring Selenium_Appium Frameworks for Seamless Integration with HeadSpin.pdf
Exploring Selenium_Appium Frameworks for Seamless Integration with HeadSpin.pdf
 

Design how objects communicate through mocking

  • 1. Design how your objects talk through mocking
  • 2. ” – Reverse Focus on the reverse mortgages “One of the most common mistakes people make is to fixate on the goal or expected outcome while ignoring their underlying behaviours.”
  • 3. @everzet • BDD Practice Manager • Software Engineer • Creator of Behat, Mink, Prophecy, PhpSpec2 • Contributor to Symfony2, Doctrine2, Composer
  • 4. This talk is about • Test-driven development with and without mocks • Introducing and making sense of different types of doubles • OOP as a messaging paradigm • Software design as a response to messaging observations • Code
  • 6. Money multiplication test from the TDD book public void testMultiplication() { Dollar five = new Dollar(5); Dollar product = five.times(2); ! assertEquals(10, product.amount); ! product = five.times(3); ! assertEquals(15, product.amount); }
  • 7. Money multiplication test in PHP public function testMultiplication() { $five = new Dollar(5); $product = $five->times(2); $this->assertEquals(10, $product->getAmount()); $product = $five->times(3); $this->assertEquals(15, $product->getAmount()); }
  • 8. Event dispatching test public function testEventIsDispatchedDuringRegistration() { $dispatcher = new EventDispatcher(); $repository = new UserRepository(); $manager = new UserManager($repository, $dispatcher); ! $timesDispatched = 0; $dispatcher->addListener( 'userIsRegistered', function() use($timesDispatched) { $timesDispatched += 1; } ); ! $user = User::signup('ever.zet@gmail.com');  $manager->registerUser($user);   $this->assertSame(1, $timesDispatched); }
  • 9. Event dispatching test public function testEventIsDispatchedDuringRegistration() { $dispatcher = new EventDispatcher(); $repository = new UserRepository(); $manager = new UserManager($repository, $dispatcher); ! $timesDispatched = 0; $dispatcher->addListener( 'userIsRegistered', function() use($timesDispatched) { $timesDispatched += 1; } ); ! $user = User::signup('ever.zet@gmail.com');  $manager->registerUser($user);   $this->assertSame(1, $timesDispatched); }
  • 10.
  • 11.
  • 12. ” – Ralph Waldo Emerson “Life is a journey, not a destination.”
  • 14. Event dispatching test public function testEventIsDispatchedDuringRegistration() { $dispatcher = new EventDispatcher(); $repository = new UserRepository(); $manager = new UserManager($repository, $dispatcher); ! $timesDispatched = 0; $dispatcher->addListener( 'userIsRegistered', function() use($timesDispatched) { $timesDispatched += 1; } ); ! $user = User::signup('ever.zet@gmail.com');  $manager->registerUser($user);   $this->assertSame(1, $timesDispatched); }
  • 15. Event dispatching collaborators public function testEventIsDispatchedDuringRegistration() { $dispatcher = new EventDispatcher(); $repository = new UserRepository(); $manager = new UserManager($repository, $dispatcher); ! $timesDispatched = 0; $dispatcher->addListener( 'userIsRegistered', function() use($timesDispatched) { $timesDispatched += 1; } ); ! $user = User::signup('ever.zet@gmail.com');  $manager->registerUser($user);   $this->assertSame(1, $timesDispatched); }
  • 16. Find the message public function testEventIsDispatchedDuringRegistration() { $dispatcher = new EventDispatcher(); $repository = new UserRepository(); $manager = new UserManager($repository, $dispatcher); ! $timesDispatched = 0; $dispatcher->addListener( 'userIsRegistered', function() use($timesDispatched) { $timesDispatched += 1; } ); ! $user = User::signup('ever.zet@gmail.com');  $manager->registerUser($user);   $this->assertSame(1, $timesDispatched); }
  • 18. ” – Alan Kay, father of OOP “OOP to me means only messaging, local retention and protection and hiding of state- process, and extreme late-binding of all things.”
  • 19. Interfaces interface LoginMessenger { public function askForCard(); public function askForPin(); }   interface InputMessenger { public function askForAccount(); public function askForAmount(); }   interface WithdrawalMessenger { public function tellNoMoney(); public function tellMachineEmpty(); }
  • 20. Doubles 1. Dummy 2. Stub 3. Spy 4. Mock 5. Fake
  • 21. Prophecy (1) use ProphecyProphet; (2) use ProphecyArgument; (3) $prophet = new Prophet(); (4) $userProphecy = $prophet->prophesize(UserInterface::class); (5) $userProphecy->changeName('everzet')->shouldBeCalled(); (6) $user = $userProphecy->reveal(); (7) $user->changeName('_md'); (8) $prophet->checkPredictions();
  • 23. 1. Dummy class System { private $authorizer;   public function __construct(Authorizer $authorizer) { $this->authorizer = $authorizer; }   public function getLoginCount() { return 0; } }
  • 24. 1. Dummy class System { private $authorizer;   public function __construct(Authorizer $authorizer) { $this->authorizer = $authorizer; }   public function getLoginCount() { return 0; } } ! public function testNewlyCreatedSystemHasNoLoggedInUsers() { $auth = $this->prophesize(Authorizer::class); $system = new System($auth->reveal()); $this->assertSame(0, $system->getLoginCount()); }
  • 25. 1. Dummy class System { private $authorizer;   public function __construct(Authorizer $authorizer) { $this->authorizer = $authorizer; }   public function getLoginCount() { return 0; } } ! public function testNewlyCreatedSystemHasNoLoggedInUsers() { $auth = $this->prophesize(Authorizer::class); $system = new System($auth->reveal()); $this->assertSame(0, $system->getLoginCount()); }
  • 27. 2. Stub class System { // ... ! public function logIn($username, $password) { if ($this->authorizer->authorize($username, $password)) { $this->loginCount++; } } ! public function getLoginCount() { return $this->loginCount; } }
  • 28. 2. Stub class System { // ... ! public function logIn($username, $password) { if ($this->authorizer->authorize($username, $password)) { $this->loginCount++; } } ! public function getLoginCount() { return $this->loginCount; } } ! public function testCountsSuccessfullyAuthorizedLogIns() { $auth = $this->prophesize(Authorizer::class); $system = new System($auth->reveal()); ! $auth->authorize('everzet', '123')->willReturn(true); ! $system->logIn('everzet', ‘123’); ! $this->assertSame(1, $system->getLoginCount()); }
  • 29. 2. Stub class System { // ... ! public function logIn($username, $password) { if ($this->authorizer->authorize($username, $password)) { $this->loginCount++; } } ! public function getLoginCount() { return $this->loginCount; } } ! public function testCountsSuccessfullyAuthorizedLogIns() { $auth = $this->prophesize(Authorizer::class); $system = new System($auth->reveal()); ! $auth->authorize('everzet', '123')->willReturn(true); ! $system->logIn('everzet', ‘123’); ! $this->assertSame(1, $system->getLoginCount()); }
  • 30. 2. Stub class System { // ... ! public function logIn($username, $password) { if ($this->authorizer->authorize($username, $password)) { $this->loginCount++; } } ! public function getLoginCount() { return $this->loginCount; } } ! public function testCountsSuccessfullyAuthorizedLogIns() { $auth = $this->prophesize(Authorizer::class); $system = new System($auth->reveal()); ! $auth->authorize('everzet', '123')->willReturn(true); ! $system->logIn(‘_md', ‘321’); ! $this->assertSame(1, $system->getLoginCount()); }
  • 32. 3. Spy class System { // ... ! public function logIn($username, $password) { if ($this->authorizer->authorize($username, $password)) { $this->loginCount++; $this->lastLoginTimer->recordLogin($username); } } }
  • 33. 3. Spy class System { // ... ! public function logIn($username, $password) { if ($this->authorizer->authorize($username, $password)) { $this->loginCount++; $this->lastLoginTimer->recordLogin($username); } } } ! public function testCountsSuccessfullyAuthorizedLogIns() { $auth = $this->prophesize(Authorizer::class); $timer = $this->prophesize(LoginTimer::class); $system = new System($auth->reveal(), $timer->reveal()); $auth->authorize('everzet', '123')->willReturn(true); ! $system->login('everzet', '123'); ! $timer->recordLogin('everzet')->shouldHaveBeenCalled(); }
  • 34. 3. Spy class System { // ... ! public function logIn($username, $password) { if ($this->authorizer->authorize($username, $password)) { $this->loginCount++; $this->lastLoginTimer->recordLogin($username); } } } ! public function testCountsSuccessfullyAuthorizedLogIns() { $auth = $this->prophesize(Authorizer::class); $timer = $this->prophesize(LoginTimer::class); $system = new System($auth->reveal(), $timer->reveal()); $auth->authorize('everzet', '123')->willReturn(true); ! $system->login('everzet', '123'); ! $timer->recordLogin('everzet')->shouldHaveBeenCalled(); }
  • 36. 3. Spy class System { // ... ! public function logIn($username, $password) { if ($this->authorizer->authorize($username, $password)) { $this->loginCount++; $this->lastLoginTimer->recordLogin($username); } } } ! public function testCountsSuccessfullyAuthorizedLogIns() { $auth = $this->prophesize(Authorizer::class); $timer = $this->prophesize(LoginTimer::class); $system = new System($auth->reveal(), $timer->reveal()); $auth->authorize('everzet', '123')->willReturn(true); ! $system->login('everzet', '123'); ! $timer->recordLogin('everzet')->shouldHaveBeenCalled(); }
  • 37. 4. Mock class System { // ... ! public function logIn($username, $password) { if ($this->authorizer->authorize($username, $password)) { $this->loginCount++; $this->lastLoginTimer->recordLogin($username); } } } ! public function testCountsSuccessfullyAuthorizedLogIns() { $auth = $this->prophesize(Authorizer::class); $timer = $this->prophesize(LoginTimer::class); $system = new System($auth->reveal(), $timer->reveal()); $auth->authorize('everzet', '123')->willReturn(true); ! $timer->recordLogin('everzet')->shouldBeCalled(); ! $system->login('everzet', '123'); ! $this->getProphet()->checkPredictions(); }
  • 38. Back to the event dispatcher
  • 39. Find the message public function testEventIsDispatchedDuringRegistration() { $dispatcher = new EventDispatcher(); $repository = new UserRepository(); $manager = new UserManager($repository, $dispatcher); ! $timesDispatched = 0; $dispatcher->addListener( 'userIsRegistered', function() use($timesDispatched) { $timesDispatched += 1; } ); ! $user = User::signup('ever.zet@gmail.com');  $manager->registerUser($user);   $this->assertSame(1, $timesDispatched); }
  • 40. Communication over state public function testEventIsDispatchedDuringRegistration() { $repository = $this->prophesize(UserRepository::class); $dispatcher = $this->prophesize(EventDispatcher::class); $manager = new UserManager( $repository->reveal(), $dispatcher->reveal() );   $user = User::signup('ever.zet@gmail.com');  $manager->registerUser($user); ! $dispatcher->dispatch('userIsRegistered', Argument::any()) ->shouldHaveBeenCalled(); }
  • 41. Exposed communication public function testEventIsDispatchedDuringRegistration() { $repository = $this->prophesize(UserRepository::class); $dispatcher = $this->prophesize(EventDispatcher::class); $manager = new UserManager( $repository->reveal(), $dispatcher->reveal() );   $user = User::signup('ever.zet@gmail.com');  $manager->registerUser($user); ! $dispatcher->dispatch('userIsRegistered', Argument::any()) ->shouldHaveBeenCalled(); }
  • 43. ” – The Observer Effect “The act of observing will influence the phenomenon being observed.”
  • 44. The 1st case: simple controller
  • 45. Simple Symfony2 controller public function packagesListAction(Request $req, User $user) { $packages = $this->getDoctrine() ->getRepository('WebBundle:Package') ->getFilteredQueryBuilder(array('maintainer' => $user->getId())) ->orderBy('p.name') ->getQuery() ->execute(); ! return $this->render('WebBundle:User:packages.html.twig', [ 'packages' => $packages ]); }
  • 46. “Simple” Symfony2 controller test public function testShowMaintainedPackages() { $request = new Request(); $user = new User('everzet'); $container = $this->prophesize(ContainerInterface::class); $doctrine = $this->prophesize(EntityManager::class); $repository = $this->prophesize(PackageRepository::class); $queryBuilder = $this->prophesize(QueryBuilder::class); $query = $this->prophesize(Query::class); $packages = [new Package('Behat'), new Package('PhpSpec')]; $templating = $this->prophesize(EngineInterface::class); $response = new Response('User packages'); ! $container->get('doctrine.orm')->willReturn($doctrine); $doctrine->getRepository('WebBundle:Package')->willReturn($repository); $repository->getFilteredQueryBuilder(['maintainer' => $user->getId()]) ->willReturn($queryBuilder); $queryBuilder->orderBy('p.name')->shouldBeCalled(); $queryBuilder->getQuery()->willReturn($query); $query->execute()->willReturn($packages); $templating->renderResponse( 'WebBundle:User:packages.html.twig', ['packages' => $packages], null) ->willReturn($response); ! $controller = new UserController(); $controller->setContainer($container); $controllerResult = $controller->maintainsPackagesAction($request, $user); ! $this->assertEquals($response, $controllerResult); }
  • 47. “Simple” Symfony2 controller test public function testShowMaintainedPackages() { $request = new Request(); $user = new User('everzet'); $container = $this->prophesize(ContainerInterface::class); $doctrine = $this->prophesize(EntityManager::class); $repository = $this->prophesize(PackageRepository::class); $queryBuilder = $this->prophesize(QueryBuilder::class); $query = $this->prophesize(Query::class); $packages = [new Package('Behat'), new Package('PhpSpec')]; $templating = $this->prophesize(EngineInterface::class); $response = new Response('User packages'); ! $container->get('doctrine.orm')->willReturn($doctrine); $doctrine->getRepository('WebBundle:Package')->willReturn($repository); $repository->getFilteredQueryBuilder(['maintainer' => $user->getId()]) ->willReturn($queryBuilder); $queryBuilder->orderBy('p.name')->shouldBeCalled(); $queryBuilder->getQuery()->willReturn($query); $query->execute()->willReturn($packages); $templating->renderResponse( 'WebBundle:User:packages.html.twig', ['packages' => $packages], null) ->willReturn($response); ! $controller = new UserController(); $controller->setContainer($container); $controllerResult = $controller->maintainsPackagesAction($request, $user); ! $this->assertEquals($response, $controllerResult); }
  • 48. “Simple” Symfony2 controller test public function testShowMaintainedPackages() { $request = new Request(); $user = new User('everzet'); $container = $this->prophesize(ContainerInterface::class); $doctrine = $this->prophesize(EntityManager::class); $repository = $this->prophesize(PackageRepository::class); $queryBuilder = $this->prophesize(QueryBuilder::class); $query = $this->prophesize(Query::class); $packages = [new Package('Behat'), new Package('PhpSpec')]; $templating = $this->prophesize(EngineInterface::class); $response = new Response('User packages'); ! $container->get('doctrine.orm')->willReturn($doctrine); $doctrine->getRepository('WebBundle:Package')->willReturn($repository); $repository->getFilteredQueryBuilder(['maintainer' => $user->getId()]) ->willReturn($queryBuilder); $queryBuilder->orderBy('p.name')->shouldBeCalled(); $queryBuilder->getQuery()->willReturn($query); $query->execute()->willReturn($packages); $templating->renderResponse( 'WebBundle:User:packages.html.twig', ['packages' => $packages], null) ->willReturn($response); ! $controller = new UserController(); $controller->setContainer($container); $controllerResult = $controller->maintainsPackagesAction($request, $user); ! $this->assertEquals($response, $controllerResult); }
  • 50. Simpler Symfony2 controller simple test public function testShowMaintainedPackages() { $user = new User('everzet'); $repository = $this->prophesize(PackageRepository::class); $templating = $this->prophesize(EngineInterface::class); $packages = [new Package('Behat'), new Package('PhpSpec')]; $response = new Response('User packages'); ! $repository->getMaintainedPackagesOrderedByName($user)->willReturn($packages); $templating->renderResponse( 'WebBundle:User:packages.html.twig', ['packages' => $packages], null) ->willReturn($response); ! $controller = new UserController($repository->reveal(), $templating->reveal()); $controllerResult = $controller->maintainsPackagesAction($user); ! $this->assertEquals($response, $controllerResult); }
  • 51. Simpler Symfony2 controller simple test public function testShowMaintainedPackages() { $user = new User('everzet'); $repository = $this->prophesize(PackageRepository::class); $templating = $this->prophesize(EngineInterface::class); $packages = [new Package('Behat'), new Package('PhpSpec')]; $response = new Response('User packages'); ! $repository->getMaintainedPackagesOrderedByName($user)->willReturn($packages); $templating->renderResponse( 'WebBundle:User:packages.html.twig', ['packages' => $packages], null) ->willReturn($response); ! $controller = new UserController($repository->reveal(), $templating->reveal()); $controllerResult = $controller->maintainsPackagesAction($user); ! $this->assertEquals($response, $controllerResult); }
  • 52. Simpler Symfony2 controller public function maintainsPackagesAction(User $user) { $packages = $this->repo->getMaintainedPackagesOrderedByName($user); ! return $this->tpl->renderResponse('WebBundle:User:packages.html.twig', [ 'packages' => $packages ]); }
  • 54. Basket checkout class Basket { // ... ! public function checkout(OrderProcessor $processor) { $totalPrice = new Price::free(); foreach ($this->getItems() as $item) { $totalPrice = $totalPrice->add($item->getPrice()); $processor->addItem($item); } ! $payment = new CashPayment::fromPrice($totalPrice); $processor->setPayment($payment);   $processor->pay(); } }
  • 55. Basket checkout test public function testCheckout() { $items = [new Item(Price::fromInt(10), Price::fromInt(5)]; $processor = $this->prophesize(OrderProcessor::class);   $basket = new Basket($items); $basket->checkout($processor->reveal());   $processor->addItem($items[0])->shouldHaveBeenCalled(); $processor->addItem($items[1])->shouldHaveBeenCalled(); $processor->setPayment(Argument::which('getPrice', 15))->shouldHaveBeenCalled(); $processor->pay()->shouldHaveBeenCalled(); }
  • 56. Basket checkout test two payments public function testCheckoutWithCash() { $items = [new Item(Price::fromInt(10), Price::fromInt(5)]; $processor = $this->prophesize(OrderProcessor::class);   $basket = new Basket($items, $credit = false); $basket->checkout($processor->reveal());   $processor->addItem($items[0])->shouldHaveBeenCalled(); $processor->addItem($items[1])->shouldHaveBeenCalled(); $processor->setPayment(Argument::allOf( Argument::type(CashPayment::class), Argument::which('getPrice', 15) ))->shouldHaveBeenCalled(); $processor->pay()->shouldHaveBeenCalled(); }   public function testCheckoutWithCreditCard() { $items = [new Item(Price::fromInt(10), Price::fromInt(5)]; $processor = $this->prophesize(OrderProcessor::class);   $basket = new Basket($items, $credit = true); $basket->checkout($processor->reveal());   $processor->addItem($items[0])->shouldHaveBeenCalled(); $processor->addItem($items[1])->shouldHaveBeenCalled(); $processor->setPayment(Argument::allOf( Argument::type(CreditPayment::class), Argument::which('getPrice', 15) ))->shouldHaveBeenCalled(); $processor->pay()->shouldHaveBeenCalled(); }
  • 57. Basket checkout test two payments public function testCheckoutWithCash() { $items = [new Item(Price::fromInt(10), Price::fromInt(5)]; $processor = $this->prophesize(OrderProcessor::class);   $basket = new Basket($items, $credit = false); $basket->checkout($processor->reveal());   $processor->addItem($items[0])->shouldHaveBeenCalled(); $processor->addItem($items[1])->shouldHaveBeenCalled(); $processor->setPayment(Argument::allOf( Argument::type(CashPayment::class), Argument::which('getPrice', 15) ))->shouldHaveBeenCalled(); $processor->pay()->shouldHaveBeenCalled(); }   public function testCheckoutWithCreditCard() { $items = [new Item(Price::fromInt(10), Price::fromInt(5)]; $processor = $this->prophesize(OrderProcessor::class);   $basket = new Basket($items, $credit = true); $basket->checkout($processor->reveal());   $processor->addItem($items[0])->shouldHaveBeenCalled(); $processor->addItem($items[1])->shouldHaveBeenCalled(); $processor->setPayment(Argument::allOf( Argument::type(CreditPayment::class), Argument::which('getPrice', 15) ))->shouldHaveBeenCalled(); $processor->pay()->shouldHaveBeenCalled(); }
  • 58. Basket checkout duplication in test public function testCheckoutWithCash() { $items = [new Item(Price::fromInt(10), Price::fromInt(5)]; $processor = $this->prophesize(OrderProcessor::class);   $basket = new Basket($items, $credit = false); $basket->checkout($processor->reveal());   $processor->addItem($items[0])->shouldHaveBeenCalled(); $processor->addItem($items[1])->shouldHaveBeenCalled(); $processor->setPayment(Argument::allOf( Argument::type(CashPayment::class), Argument::which('getPrice', 15) ))->shouldHaveBeenCalled(); $processor->pay()->shouldHaveBeenCalled(); }   public function testCheckoutWithCreditCard() { $items = [new Item(Price::fromInt(10), Price::fromInt(5)]; $processor = $this->prophesize(OrderProcessor::class);   $basket = new Basket($items, $credit = true); $basket->checkout($processor->reveal());   $processor->addItem($items[0])->shouldHaveBeenCalled(); $processor->addItem($items[1])->shouldHaveBeenCalled(); $processor->setPayment(Argument::allOf( Argument::type(CreditPayment::class), Argument::which('getPrice', 15) ))->shouldHaveBeenCalled(); $processor->pay()->shouldHaveBeenCalled(); }
  • 60. Basket checkout test simplification public function testCheckoutWithPaymentMethod() { $items = [new Item(Price::fromInt(10), Price::fromInt(5)]; $processor = $this->prophesize(OrderProcessor::class); $paymentMethod = $this->prophesize(PaymentMethod::class); $payment = $this->prophesize(Payment::class);   $paymentMethod->acceptPayment(Price::fromInt(15))->willReturn($payment);   $basket = new Basket($items); $basket->checkout($processor->reveal(), $paymentMethod->reveal());   $processor->addItem($items[0])->shouldHaveBeenCalled(); $processor->addItem($items[1])->shouldHaveBeenCalled(); $processor->setPayment($payment)->shouldHaveBeenCalled(); $processor->pay()->shouldHaveBeenCalled(); }
  • 61. Basket checkout test simplification public function testCheckoutWithPaymentMethod() { $items = [new Item(Price::fromInt(10), Price::fromInt(5)]; $processor = $this->prophesize(OrderProcessor::class); $paymentMethod = $this->prophesize(PaymentMethod::class); $payment = $this->prophesize(Payment::class);   $paymentMethod->acceptPayment(Price::fromInt(15))->willReturn($payment);   $basket = new Basket($items); $basket->checkout($processor->reveal(), $paymentMethod->reveal());   $processor->addItem($items[0])->shouldHaveBeenCalled(); $processor->addItem($items[1])->shouldHaveBeenCalled(); $processor->setPayment($payment)->shouldHaveBeenCalled(); $processor->pay()->shouldHaveBeenCalled(); }
  • 62. Final basket checkout class Basket { // ...   public function checkout(OrderProcessor $processor, PaymentMethod $method) { $totalPrice = new Price::free(); foreach ($this->getItems() as $item) { $totalPrice = $totalPrice->add($item->getPrice()); $processor->addItem($item); }   $payment = $method->acceptPayment($totalPrice); $processor->setPayment($payment);   $processor->pay(); } }
  • 63. Final basket checkout class Basket { // ...   public function checkout(OrderProcessor $processor, PaymentMethod $method) { $totalPrice = new Price::free(); foreach ($this->getItems() as $item) { $totalPrice = $totalPrice->add($item->getPrice()); $processor->addItem($item); }   $payment = $method->acceptPayment($totalPrice); $processor->setPayment($payment);   $processor->pay(); } }
  • 65. Browser class Browser { public function __construct(BrowserDriver $driver) { $this->driver = $driver; }   public function goto($url) { $this->driver->boot(); $this->driver->visit($url); } }
  • 66. Browser drivers interface BrowserDriver { public function boot(); public function visit($url); } ! interface HeadlessBrowserDriver extends BrowserDriver {} ! class SeleniumDriver implements BrowserDriver { public function boot() { $this->selenium->startBrowser($this->browser); } ! public function visit($url) { $this->selenium->visitUrl($url); } } ! class GuzzleDriver implements HeadlessBrowserDriver { public function boot() {} ! public function visit($url) { $this->guzzle->openUrl($url); } }
  • 67. Headless driver test public function testVisitingProvidedUrl() { $url = 'http://en.wikipedia.org'; $driver = $this->prophesize(HeadlessBrowserDriver::class); ! $driver->visit($url)->shouldBeCalled(); ! $browser = new Browser($driver->reveal()); $browser->goto($url); ! $this->getProphecy()->checkPredictions(); }
  • 68. Failing headless driver test public function testVisitingProvidedUrl() { $url = 'http://en.wikipedia.org'; $driver = $this->prophesize(HeadlessBrowserDriver::class); ! $driver->visit($url)->shouldBeCalled(); ! $browser = new Browser($driver->reveal()); $browser->goto($url); ! $this->getProphecy()->checkPredictions(); }
  • 70. Headless driver implementation class GuzzleDriver implements HeadlessBrowserDriver { ! public function boot() {} ! public function visit($url) { $this->guzzle->openUrl($url); } }
  • 71. Headless driver simple behaviour class GuzzleDriver implements HeadlessBrowserDriver { ! public function boot() {} ! public function visit($url) { $this->guzzle->openUrl($url); } }
  • 72. Headless driver that knows about booting class GuzzleDriver implements HeadlessBrowserDriver { ! public function boot() { $this->allowDoActions = true; }   public function visit($url) { if ($this->allowDoActions) $this->guzzle->openUrl($url); } }
  • 74. Adapter layer between BrowserDriver and HeadlessBrowserDriver interface HeadlessBrowserDriver { public function visit($url); } ! class GuzzleDriver implements HeadlessBrowserDriver { public function visit($url) { $this->guzzle->openUrl($url); } } ! final class HeadlessBrowserAdapter implements BrowserDriver { private $headlessDriver, $allowDoAction = false; ! public function __construct(HeadlessBrowserDriver $headlessDriver) { $this->headlessDriver = $headlessDriver; } ! public function boot() { $this->allowDoActions = true; } ! public function visit($url) { if ($this->allowDoActions) $this->headlessDriver->visit($url); } }
  • 75. Dirty adapter layer between BrowserDriver and HeadlessBrowserDriver interface HeadlessBrowserDriver { public function visit($url); } ! class GuzzleDriver implements HeadlessBrowserDriver { public function visit($url) { $this->guzzle->openUrl($url); } } ! final class HeadlessBrowserAdapter implements BrowserDriver { private $headlessDriver, $allowDoAction = false; ! public function __construct(HeadlessBrowserDriver $headlessDriver) { $this->headlessDriver = $headlessDriver; } ! public function boot() { $this->allowDoActions = true; } ! public function visit($url) { if ($this->allowDoActions) $this->headlessDriver->visit($url); } }
  • 76. Single adapter layer between BrowserDriver and HeadlessBrowserDriver interface HeadlessBrowserDriver { public function visit($url); } ! class GuzzleDriver implements HeadlessBrowserDriver { public function visit($url) { $this->guzzle->openUrl($url); } } ! final class HeadlessBrowserAdapter implements BrowserDriver { private $headlessDriver, $allowDoAction = false; ! public function __construct(HeadlessBrowserDriver $headlessDriver) { $this->headlessDriver = $headlessDriver; } ! public function boot() { $this->allowDoActions = true; } ! public function visit($url) { if ($this->allowDoActions) $this->headlessDriver->visit($url); } }
  • 78. ATM messenger interface interface Messenger { public function askForCard(); public function askForPin(); public function askForAccount(); public function askForAmount(); public function tellNoMoney(); public function tellMachineEmpty(); }
  • 79. City ATM login test public function testAtmAsksForCardAndPinDuringLogin() { $messenger = $this->prophesize(Messenger::class); ! $messenger->askForCard()->shouldBeCalled(); $messenger->askForPin()->shouldBeCalled(); ! $atm = new CityAtm($messenger->reveal()); $atm->login(); ! $this->getProphet()->checkPredictions(); }
  • 80. City ATM login test public function testAtmAsksForCardAndPinDuringLogin() { $messenger = $this->prophesize(Messenger::class); ! $messenger->askForCard()->shouldBeCalled(); $messenger->askForPin()->shouldBeCalled(); ! $atm = new CityAtm($messenger->reveal()); $atm->login(); ! $this->getProphet()->checkPredictions(); }
  • 82. City ATM login test public function testAtmAsksForCardAndPinDuringLogin() { $messenger = $this->prophesize(LoginMessenger::class); ! $messenger->askForCard()->shouldBeCalled(); $messenger->askForPin()->shouldBeCalled(); ! $atm = new CityAtm($messenger->reveal()); $atm->login(); ! $this->getProphet()->checkPredictions(); }
  • 83. ATM messenger interface(s) interface LoginMessenger { public function askForCard(); public function askForPin(); } ! interface InputMessenger { public function askForAccount(); public function askForAmount(); } ! interface WithdrawalMessenger { public function tellNoMoney(); public function tellMachineEmpty(); } ! interface Messenger extends LoginMessenger, InputMessenger, WithdrawalMessenger
  • 84. The 5th case: entity repository
  • 85. Doctrine entity repository class JobRepository extends EntityRepository { public function findJobByName($name) { return $this->findOneBy(['name' => $name]); } }
  • 86. Doctrine entity repository test public function testFindingJobsByName() { $em = $this->prophesize('EntityManager'); $cmd = $this->prophesize('ClassMetadata'); $uow = $this->prophesize('UnitOfWork'); $ep = $this->prophesize('EntityPersister'); $job = Job::fromName('engineer'); ! $em->getUnitOfWork()->willReturn($uow); $uow->getEntityPersister(Argument::any())->willReturn($ep); $ep->load(['name' => 'engineer'], null, null, [], null, 1, null) ->willReturn($job); ! $repo = new JobRepository($em->reveal(), $cmd->reveal()); $actualJob = $repo->findJobByName('engineer'); ! $this->assertSame($job, $actualJob); }
  • 87. Doctrine entity repository test public function testFindingJobsByName() { $em = $this->prophesize('EntityManager'); $cmd = $this->prophesize('ClassMetadata'); $uow = $this->prophesize('UnitOfWork'); $ep = $this->prophesize('EntityPersister'); $job = Job::fromName('engineer'); ! $em->getUnitOfWork()->willReturn($uow); $uow->getEntityPersister(Argument::any())->willReturn($ep); $ep->load(['name' => 'engineer'], null, null, [], null, 1, null) ->willReturn($job); ! $repo = new JobRepository($em->reveal(), $cmd->reveal()); $actualJob = $repo->findJobByName('engineer'); ! $this->assertSame($job, $actualJob); }
  • 88. Do not mock things you do not own
  • 89. Doctrine entity repository test public function testFindingJobsByName() { $em = $this->prophesize('EntityManager'); $cmd = $this->prophesize('ClassMetadata'); $uow = $this->prophesize('UnitOfWork'); $ep = $this->prophesize('EntityPersister'); $job = Job::fromName('engineer'); ! $em->getUnitOfWork()->willReturn($uow); $uow->getEntityPersister(Argument::any())->willReturn($ep); $ep->load(['name' => 'engineer'], null, null, [], null, 1, null) ->willReturn($job); ! $repo = new JobRepository($em->reveal(), $cmd->reveal()); $actualJob = $repo->findJobByName('engineer'); ! $this->assertSame($job, $actualJob); }
  • 91. Job repository & Doctrine implementation of it interface  JobRepository  {          public  function  findJobByName($name);   }   ! class  DoctrineJobRepository  extends  EntityRepository                                                          implements  JobRepository  {   !        public  function  findJobByName($name)  {                  return  $this-­‐>findOneBy(['name'  =>  $name]);          }   }
  • 92. Job repository & Doctrine implementation of it interface  JobRepository  {          public  function  findJobByName($name);   }   ! class  DoctrineJobRepository  extends  EntityRepository                                                          implements  JobRepository  {   !        public  function  findJobByName($name)  {                  return  $this-­‐>findOneBy(['name'  =>  $name]);          }   }
  • 93. Job repository & Doctrine implementation of it interface  JobRepository  {          public  function  findJobByName($name);   }   ! class  DoctrineJobRepository  extends  EntityRepository                                                          implements  JobRepository  {   !        public  function  findJobByName($name)  {                  return  $this-­‐>findOneBy(['name'  =>  $name]);          }   }
  • 95. Recap: 1. State-focused TDD is not the only way to TDD
  • 96. Recap: 1. State-focused TDD is not the only way to TDD 2. Messaging is far more important concept of OOP than the state
  • 97. Recap: 1. State-focused TDD is not the only way to TDD 2. Messaging is far more important concept of OOP than the state 3. By focusing on messaging, you expose messaging problems
  • 98. Recap: 1. State-focused TDD is not the only way to TDD 2. Messaging is far more important concept of OOP than the state 3. By focusing on messaging, you expose messaging problems 4. By exposing messaging problems, you could discover most of the SOLID principles violation before they happen
  • 99. Recap: 1. State-focused TDD is not the only way to TDD 2. Messaging is far more important concept of OOP than the state 3. By focusing on messaging, you expose messaging problems 4. By exposing messaging problems, you could discover most of the SOLID principles violation before they happen 5. Prophecy is awesome
  • 101.
  • 102.