Slide 1

Slide 1 text

@jcarouth / #lsp13 also known as testing from the pit of despair Introducing Tests in Legacy PHP Applications Friday, June 28, 13

Slide 2

Slide 2 text

Howdy! I am Jeff Carouth I have written many, many lines of legacy code. @jcarouth I work at Liftopia employing the techniques I am going to present today. Friday, June 28, 13

Slide 3

Slide 3 text

What He Said Friday, June 28, 13

Slide 4

Slide 4 text

Legacy Code? Friday, June 28, 13

Slide 5

Slide 5 text

Legacy Code IS difficult to change often unstructured and likely incomprehensible “Test resistant” Friday, June 28, 13

Slide 6

Slide 6 text

$body->compile_dir = $body->template_dir."/compiled"; //determine the controller that should be used for this request based on which // controller name is passed through the query string. If there is not a // controller name in the query string, we will assume that the request is for // the index page if ( true == isset( $_GET['controller'] ) && true == is_string( $_GET['controller']) && $_GET['c $page = trim( $_GET['controller'] ); unset( $_GET['controller'] ); } else { $page = ""; } // If we have a separate intro page defined, it should be rendered. if( SEPARATE_INTRO !== false && $page === "" ) { if( true == is_file( "controllers/" . SEPARATE_INTRO . ".php" ) ) { include_once "controllers/" . SEPARATE_INTRO . ".php"; } $body->display( SEPARATE_INTRO . ".html" ); } else { $content = new Smarty(); $content->template_dir = "html"; $content->compile_dir = $body->template_dir."/compiled"; if( $page === "" ) { $page = DEFAULT_PAGE; } // Determine what, if any, view should be presented to the client. If there is // not a view request assign an empty string (default view). Friday, June 28, 13

Slide 7

Slide 7 text

Should Vs. Must Friday, June 28, 13

Slide 8

Slide 8 text

RESIST the urge to Rewrite Friday, June 28, 13

Slide 9

Slide 9 text

REFACTOR only what is necessary for tests Friday, June 28, 13

Slide 10

Slide 10 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 Friday, June 28, 13

Slide 11

Slide 11 text

Break Dependencies Without Tests In Place? Migraine. Friday, June 28, 13

Slide 12

Slide 12 text

Mechanism For Feedback Friday, June 28, 13

Slide 13

Slide 13 text

Acceptance Tests masquerade as pinning tests Friday, June 28, 13

Slide 14

Slide 14 text

Friday, June 28, 13

Slide 15

Slide 15 text

Friday, June 28, 13

Slide 16

Slide 16 text

//features/registereduser.feature Feature: Registered users can run fizzbuzz As a registered fizzbuzzer I want to be able to run fizzbuzz Background: Given An account exists for user "behat" password "letmein" When I am on homepage And I log in as "behat" with password "letmein" Scenario: Run fizzbuzz past anonymous limit When I run fizzbuzz on range "95" to "102" Then I should see "Buzz Fizz 97 98 Fizz Buzz 101 Fizz" Scenario: Cannot exceed maximum range When I run fizzbuzz on range "1" to "302" Then I should see "The range you requested is beyond the maximum… And I should see "298 299 FizzBuzz 301" But I should not see "299 FizzBuzz 301 302" Friday, June 28, 13

Slide 17

Slide 17 text

//features/bootstrap/FeatureContext.php

Slide 18

Slide 18 text

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. Friday, June 28, 13

Slide 19

Slide 19 text

The FizzBuzzer Because, really, people will fund anything Friday, June 28, 13

Slide 20

Slide 20 text

Friday, June 28, 13

Slide 21

Slide 21 text

Anonymous users can run FizzBuzz but only up to to the number 100 Friday, June 28, 13

Slide 22

Slide 22 text

Whether the job is big or small, do it right or not at all Friday, June 28, 13

Slide 23

Slide 23 text

Identify Change Points Friday, June 28, 13

Slide 24

Slide 24 text

FizzBuzz!

Result

Friday, June 28, 13

Slide 25

Slide 25 text

Write Pinning Tests Friday, June 28, 13

Slide 26

Slide 26 text

Feature: Anonymous Run FizzBuzz As a non-registered fizzbuzz enthusiast I need to be able to run fizzbuzz on a range of numbers So that I can get my fix of fizzbuzziness Scenario: Anonymous users should be able to run the fizzbuzzer When I am on homepage And I run fizzbuzz on range "12" to "20" Then I should see "Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz" Scenario: Anonymous users should be able to run a second fizzbuzz When I am on homepage And I run fizzbuzz on range "3" to "5" Then I should see "Fizz 4 Buzz" When I run fizzbuzz on range "9" to "12" Then I should see "Fizz Buzz 11 Fizz" Friday, June 28, 13

Slide 27

Slide 27 text

Friday, June 28, 13

Slide 28

Slide 28 text

Refactor and Make Changes Friday, June 28, 13

Slide 29

Slide 29 text

Then I should see "Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz" Scenario: Anonymous users should be able to run a second fizzbuzz When I am on homepage And I run fizzbuzz on range "3" to "5" Then I should see "Fizz 4 Buzz" When I run fizzbuzz on range "9" to "12" Then I should see "Fizz Buzz 11 Fizz" Scenario: Anonymous users should not be able to exceed 100 When I am on homepage And I run fizzbuzz on range "95" to "101" Then I should see "101 is beyond the maximum value, 100" And I should see "Buzz Fizz 97 98 Fizz Buzz" Friday, June 28, 13

Slide 30

Slide 30 text

Friday, June 28, 13

Slide 31

Slide 31 text

class FizzBuzzTest extends \PHPUnit_Framework_TestCase { /** * @test */ public function normalizeRangeForAnonShouldTruncateAt100() { $normalizedRange = fizzbuzz_normalize_range(95, 101, false); $this->assertTrue($normalizedRange['can_process']); $this->assertEquals(100, $normalizedRange['end']); } /** * @test */ public function signedInUsersCanUseIntegersOver100() { $range = fizzbuzz_normalize_range(95, 101, true); $this->assertTrue($range['can_process']); $this->assertEquals(101, $range['end']); } } Friday, June 28, 13

Slide 32

Slide 32 text

function fizzbuzz_normalize_range($start, $end, $is_user = false) { $can_process = true; $message = ''; if (!$is_user) { //anon can only fizzbuzz for ranges up to 100 if ($end > 100) { $message = '$end.'is 'beyond the maximum value, 100” $end = 100; } } return array( 'can_process' => $can_process, 'message' => $message, 'start' => $start, 'end' => $end, ); } Friday, June 28, 13

Slide 33

Slide 33 text

Friday, June 28, 13

Slide 34

Slide 34 text

Friday, June 28, 13

Slide 35

Slide 35 text

Friday, June 28, 13

Slide 36

Slide 36 text

summary Friday, June 28, 13

Slide 37

Slide 37 text

Freedom to Refactor FEARLESSLY Friday, June 28, 13

Slide 38

Slide 38 text

Create Abstractions expose the API you want, guided by tests Friday, June 28, 13

Slide 39

Slide 39 text

Friday, June 28, 13

Slide 40

Slide 40 text

rangeProcessor = new \FizzBuzzer\RangeProcessor(); } /** * @test */ public function shouldReturnValuesForSimpleRange() { $this->assertEquals( array(1, 2, 'Fizz', 4, 'Buzz'), $this->rangeProcessor->process(1, 5) ); } } Friday, June 28, 13

Slide 41

Slide 41 text

class RangeProcessor { public function process($start, $end) { $results = array(); foreach (range($start, $end) as $number) { $results[] = $this->fizzbuzzFor($number); } return $results; } private function fizzbuzzFor($number) { if ($number % 15 === 0) { return 'FizzBuzz'; } elseif ($number % 3 === 0) { return 'Fizz'; } elseif ($number % 5 === 0) { return 'Buzz'; } return $number; } } Friday, June 28, 13

Slide 42

Slide 42 text

function fizzbuzz_for_range($start, $end) { $f = array(); for ($i = $start; $i <= $end; $i++) { $f[] = fizzbuzz_of($i); } return $f; } Friday, June 28, 13

Slide 43

Slide 43 text

function fizzbuzz_for_range($start, $end) { $processor = new RangeProcessor(); return $processor->process($start, $end); } Friday, June 28, 13

Slide 44

Slide 44 text

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. Friday, June 28, 13

Slide 45

Slide 45 text

Thank You! Questions? http://speakerdeck.com/jcarouth @jcarouth | [email protected] https://joind.in/8683 Friday, June 28, 13