Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Testing Strategies for Legacy PHP Applications

Testing Strategies for Legacy PHP Applications

You know testing is beneficial to your project. You are familiar with merits and caveats of test-driven development. But the project you're hacking on right now is what most would call a legacy application. How do you apply your test knowledge to an application that doesn't lend itself to traditional unit testing? The answer most will give is, "you don't," but we're going to look at ways to write tests now that will allow you to improve and refactor your application to evolve the code to a more manageable state. The tools are easy to find and simple examples are plentiful, let's find out how we can apply them to more complex situations.

Jeff Carouth

August 15, 2012
Tweet

More Decks by Jeff Carouth

Other Decks in Programming

Transcript

  1. Testing Strategies for Legacy PHP Applications exploring how to test

    the untestable Jeff Carouth Wednesday, August 15, 12
  2. 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
  3. Legacy Code IS difficult to change often unstructured and likely

    incomprehensible “Test resistant” Wednesday, August 15, 12
  4. Legacy Code Dilemma You should have tests in place before

    making changes. You must make changes to begin writing tests. Wednesday, August 15, 12
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. class OrderRepository { public function __construct($db = null) { if

    (null === $db) { $db = new DbConnection(); } $this->db = $db; } } Wednesday, August 15, 12
  12. 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
  13. class OrderRepository { public function filterByPurchaserEmail( $email, $purchaserRepository = null

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

    'DbConnection', array('findOrderByCustomerId') ); $customerRepositoryMock = $this->getMock( 'CustomerRepository', array('filterByEmail') ); // ...snip... } } Wednesday, August 15, 12
  15. 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
  16. 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
  17. 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
  18. 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