Slide 1

Slide 1 text

Testing Wizardry how mocks and fixtures can save your sanity Jeff Carouth #ZendCon 2012

Slide 2

Slide 2 text

Jeff Carouth tweets jcarouth blogs http://carouth.com checks [email protected] works

Slide 3

Slide 3 text

Writing Tests Is easy

Slide 4

Slide 4 text

Is more difficult Writing Effective Tests

Slide 5

Slide 5 text

Unit Tests Vs. Integration Tests

Slide 6

Slide 6 text

Fake or Simulated object exposing the same interface as the object it mimics

Slide 7

Slide 7 text

Why Mocks? Objects which have states that are difficult to create or reproduce.

Slide 8

Slide 8 text

Mocks and Stubs with PHPUnit http://crth.net/phpunit-mock-docs

Slide 9

Slide 9 text

interface MyInterface { public function sayHi(); } class FirstMockTest extends PHPUnit_Framework_TestCase { public function testJeffShouldBeAbleToMockHisInterface() { $mockMyInterface = $this->getMock('MyInterface'); $mockMyInterface->expects($this->any()) ->method('sayHi') ->will($this->returnValue('Oh, hi!')); $this->assertEquals('Oh, hi!', $mockMyInterface->sayHi()); } }

Slide 10

Slide 10 text

Stubs (in PHPUnit) Can: returnValue(‘hello world!’) returnArgument(1) returnSelf() returnValueMap($map) returnCallback(‘str_rot13’) throwException(new OopsException())

Slide 11

Slide 11 text

class OrderTest extends PHPUnit_Framework_TestCase { public function testSaveOrderSendsEmailWithOrderConfirmation() { $order = new Order(new \EmailService\Service\Sendmail()); $order->setId(12345); $order->save(); // log into email account // find email in inbox // verify it has the correct subject // No. No. No. } } NO!

Slide 12

Slide 12 text

class OrderTest extends PHPUnit_Framework_TestCase { public function testSaveOrderSendsEmailWithOrderConfirmation() { $mockEmailService = $this->getMock( '\\EmailService\\EmailServiceInterface' ); $mockEmailService->expects($this->once()) ->method('send'); $order = new Order($mockEmailService); $order->setId(12345); $order->save(); } }

Slide 13

Slide 13 text

class Order { private $emailService; private $logger; private $id; public function __construct($service, $logger = null) { $this->emailService = $service; $this->logger = $logger; } public function save() { //do some saving logic $this->emailService->send( 'Order Confirmation #' . $this->id ); } }

Slide 14

Slide 14 text

class OrderTest extends PHPUnit_Framework_TestCase { //...snip... public function testEmailConfirmationFailLogs() { $mockLogger = $this->getMock('\\System\\LoggerInterface'); $mockLogger->expects($this->once()) ->method('log'); $mockEmailService = $this->getMock( '\\EmailService\\EmailServiceInterface' ); $mockEmailService->expects($this->once()) ->method('send') ->will($this->throwException(new Exception())); $order = new Order($mockEmailService, $mockLogger); $order->setId(12345); $order->save(); } }

Slide 15

Slide 15 text

class Order { public function save() { //do some saving logic try { $this->emailService->send( 'Order Confirmation #' . $this->id ); } catch (Exception $e) { if (null !== $this->logger) { $this->logger->log( "email failed for order {$this->id}" ); } } } }

Slide 16

Slide 16 text

Phake http://phake.digitalsandwich.com/docs/html/

Slide 17

Slide 17 text

class OrderTest extends PHPUnit_Framework_TestCase { public function testSaveOrderSendsEmailWithOrderConfirmation() { $mockEmailService = Phake::mock( '\\EmailService\\EmailServiceInterface' ); Phake::when($mockEmailService)->send()->thenReturn(true); $order = new Order($mockEmailService); $order->setId(12345); $order->save(); Phake::verify($mockEmailService)->send(); } }

Slide 18

Slide 18 text

Mockery https://github.com/padraic/mockery

Slide 19

Slide 19 text

class OrderTest extends PHPUnit_Framework_TestCase { public function tearDown() { m::close(); } public function testSaveOrderSendsEmailWithOrderConfirmation() { $mockEmailService = \Mockery::mock( '\\EmailService\\EmailServiceInterface' ); $mockEmailService->shouldReceive('send') ->withAnyArgs() ->andReturn(true); $order = new Order($mockEmailService); $order->setId(12345); $order->save(); } }

Slide 20

Slide 20 text

Fixtures Defined

Slide 21

Slide 21 text

DbUnit

Slide 22

Slide 22 text

class TodoListTest extends PHPUnit_Extensions_Database_TestCase { private $pdo; /** * @return PHPUnit_Extensions_Database_DB_IDatabaseConnection */ public function getConnection() { $this->pdo = new PDO('DSN', 'USER', 'PASS'); return $this->createDefaultDBConnection( $this->pdo, 'todolist'); } /** * @return PHPUnit_Extensions_Database_DataSet_IDataSet */ public function getDataSet() { return $this->createFlatXMLDataSet( __DIR__ . "/fixtures/todo-seed.xml"); } }

Slide 23

Slide 23 text

Slide 24

Slide 24 text

class TodoListTest extends PHPUnit_Extensions_Database_TestCase { public function testGetCountReturnNumberOfUnfinishedTasks() { $todoModel = new TodoModel($this->pdo); $expectedNumberOfTodos = 4; $todos = $todoModel->getUnfinishedTasks(); $this->assertCount( $expectedNumberOfTodos, $todos, 'The expected number of unfinished tasks is wrong.' ); } }

Slide 25

Slide 25 text

class TodoModel { private $db; public function __construct($db) { $this->db = $db; } public function getUnfinishedTasks() { $sql = "SELECT * FROM todo"; $result = $this->db->query($sql); return $result->fetchAll(); } }

Slide 26

Slide 26 text

Phabric https://github.com/benwaine/Phabric

Slide 27

Slide 27 text

class FeatureContext extends MinkContext { protected $phabric; public function __construct(array $parameters) { $config = new \Doctrine\DBAL\Configuration(); self::$db = \Doctrine\DBAL\DriverManager::getConnection(...); $datasource = new \Phabric\Datasource\Doctrine( self::$db, $parameters['Phabric']['entities'] ); $this->phabric = new Phabric\Phabric($datasource); } }

Slide 28

Slide 28 text

Feature: As an organized person I want to be able to manage my todo list So that I can ensure I accomplish my tasks Scenario: I want to see all incomplete tasks Given The following tasks exist | summary | createdDate | completed | estimate | | Task 1 | 2012-09-21 19:53:33 | false | 1 | | Task 2 | 2012-09-21 19:53:33 | false | 1 | | Task 3 | 2012-09-21 19:53:33 | false | 1 | When I am on "/tasks" Then I should see "3" tasks /** * @Given /^The following tasks exist$/ */ public function theFollowingTaskssExist(TableNode $table) { $this->phabric->insertFromTable('tasks', $table); }

Slide 29

Slide 29 text

Faker https://github.com/fzaninotto/Faker

Slide 30

Slide 30 text

' . PHP_EOL; print '' . PHP_EOL; for ($i = 0; $i < 50; $i++) { print " firstName}\" " . "lastname=\"{$faker->lastName}\" " . "email=\"{$faker->email}\" />" . PHP_EOL; } print '';

Slide 31

Slide 31 text

Slide 32

Slide 32 text

Thank You! https://joind.in/7039 @jcarouth | [email protected] http://speakerdeck.com/jcarouth