Slide 1

Slide 1 text

Testing Strategies for Legacy PHP Applications exploring how to test the untestable Jeff Carouth Wednesday, August 15, 12

Slide 2

Slide 2 text

Howdy! My name is Jeff I tweet as @jcarouth I blog at http://carouth.com I develop software at I test code because I’m not perfect Wednesday, August 15, 12

Slide 3

Slide 3 text

Legacy Code IS difficult to change often unstructured and likely incomprehensible “Test resistant” Wednesday, August 15, 12

Slide 4

Slide 4 text

Legacy Code Dilemma You should have tests in place before making changes. You must make changes to begin writing tests. Wednesday, August 15, 12

Slide 5

Slide 5 text

Where Do I Start? Wednesday, August 15, 12

Slide 6

Slide 6 text

Wednesday, August 15, 12

Slide 7

Slide 7 text

RESIST the urge to Rewrite Wednesday, August 15, 12

Slide 8

Slide 8 text

Legacy Code Change Algorithm 1. Identify change points. 2. Find test points. 3. Break dependencies. 4. Write tests. 5. Make changes and refactor. Source: Working Effectively with Legacy Code by Michael Feathers Wednesday, August 15, 12

Slide 9

Slide 9 text

RESIST the urge to Rewrite REFACTOR only what is necessary for tests Wednesday, August 15, 12

Slide 10

Slide 10 text

In order to refactor As a developer dealing with legacy code I need a mechanism for feedback So that I can make changes fearlessly Wednesday, August 15, 12

Slide 11

Slide 11 text

In order to refactor As a developer dealing with legacy code I need a mechanism for feedback So that I can make changes fearlessly acceptance tests Wednesday, August 15, 12

Slide 12

Slide 12 text

Legacy Code Test Introduction Algorithm 1. Identify what you need to change. 2. Write pinning test for impacted behavior. 3. Locate test point for unit test. 4. Break dependencies. 5. Write tests. 6. Make changes and refactor. 7. Repeat 5 and 6 until change is completed. Wednesday, August 15, 12

Slide 13

Slide 13 text

Cannot Filter Orders By Purchaser Q: What do we need to change? A: The OrderRepository does not correctly filter by purchaser. Wednesday, August 15, 12

Slide 14

Slide 14 text

Feature: Orders reporting In order to see how our product inventory moves As a store manager I need to be able to access order records by certain criteria Scenario: Filter orders by purchaser email Given There are 5 orders placed by '[email protected]' And There are 15 orders placed in the past 5 days When I request '/orders/by/purchaser/value/[email protected]' Then I should see 5 total orders Wednesday, August 15, 12

Slide 15

Slide 15 text

class OrderRepository { public function __construct() { $this->db = new DbConnection(); } } Wednesday, August 15, 12

Slide 16

Slide 16 text

class OrderRepository { public function __construct($db = null) { if (null === $db) { $db = new DbConnection(); } $this->db = $db; } } Wednesday, August 15, 12

Slide 17

Slide 17 text

class OrderRepository { public function filterByPurchaserEmail($email) { $purchaserRepository = new CustomerRepository($this->db); $purchaserRepository->findByEmail($email); if (count($purchasers) == 0) { return array(); } $orders = array(); foreach ($purchasers as $purchaser) { $orders = $this->db->findByCustomerId($purchaser['id']); } return $orders; } } Wednesday, August 15, 12

Slide 18

Slide 18 text

class OrderRepository { public function filterByPurchaserEmail( $email, $purchaserRepository = null ) { if (null === $purchaserRepository) { $purchaserRepository = new CustomerRepository($this->db); } $purchaserRepository->filterByEmail($email); // ...snip... } } Wednesday, August 15, 12

Slide 19

Slide 19 text

class OrderRepositoryTest { public function testFilterByPurchaserShouldReturnOrders() { $dbMock = $this->getMock( 'DbConnection', array('findOrderByCustomerId') ); $customerRepositoryMock = $this->getMock( 'CustomerRepository', array('filterByEmail') ); // ...snip... } } Wednesday, August 15, 12

Slide 20

Slide 20 text

class OrderRepositoryTest { public function testFilterByPurchaserShouldReturnOrders() { // ...snip... $dbMock->expects($this->atLeastOnce()) ->method('findOrderByCustomerId') ->with($this->LogicalOr( $this->equalTo($customerIds[0]), $this->equalTo($customerIds[1]) )) ->will($this->returnCallback( function ($customerId) use($testOrders) { return $testOrders[$customerId]; } )); // ...snip... } } Wednesday, August 15, 12

Slide 21

Slide 21 text

class OrderRepositoryTest { public function testFilterByPurchaserShouldReturnOrders() { // ...snip... $orderRepository = new OrderRepository($dbMock); $orders = $orderRepository->filterByPurchaserEmail( '[email protected]', $customerRepositoryMock ); $this->assertEquals(7, count($orders)); } } Wednesday, August 15, 12

Slide 22

Slide 22 text

class OrderRepository { public function filterByPurchaserEmail($email) { $purchaserRepository = new CustomerRepository($this->db); $purchaserRepository->findByEmail($email); if (count($purchasers) == 0) { return array(); } $orders = array(); foreach ($purchasers as $purchaser) { $orders = $this->db->findByCustomerId($purchaser['id']); } return $orders; } } Wednesday, August 15, 12

Slide 23

Slide 23 text

class OrderRepository { public function filterByPurchaserEmail($email) { $purchaserRepository = new CustomerRepository($this->db); $purchaserRepository->findByEmail($email); if (count($purchasers) == 0) { return array(); } $orders = array(); foreach ($purchasers as $purchaser) { $neworders = $this->db->findByCustomerId($purchaser['id']); $orders = $orders + $neworders; } return $orders; } } Wednesday, August 15, 12

Slide 24

Slide 24 text

Freedom to Refactor FEARLESSLY Wednesday, August 15, 12

Slide 25

Slide 25 text

Thank You! Questions? do you have or Comments? @jcarouth | [email protected] Wednesday, August 15, 12

Slide 26

Slide 26 text

Wednesday, August 15, 12

Slide 27

Slide 27 text

Resources Acceptance Testing Behat – http://behat.org Mink – http://mink.behat.org Selenium – http://seleniumhq.org/ Reading Wednesday, August 15, 12