Slide 1

Slide 1 text

also known as testing from the pit of despair Introducing Tests in Legacy PHP Applications PRESENTED BY JEFF CAROUTH @jcarouth

Slide 2

Slide 2 text

What He Said

Slide 3

Slide 3 text

Legacy 1.

Slide 4

Slide 4 text

Legacy Code IS difficult to change often unstructured and likely incomprehensible “Test resistant”

Slide 5

Slide 5 text

Should Vs. Must dilemma

Slide 6

Slide 6 text

RESIST the urge to Rewrite

Slide 7

Slide 7 text

REFACTOR only what is necessary for tests

Slide 8

Slide 8 text

Algorithms 2.

Slide 9

Slide 9 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

Slide 10

Slide 10 text

Break Dependencies Without Tests In Place? Migraine.

Slide 11

Slide 11 text

Mechanism For Feedback

Slide 12

Slide 12 text

Acceptance Tests masquerade as characterization tests

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 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"!

Slide 16

Slide 16 text

//features/bootstrap/FeatureContext.php

Slide 17

Slide 17 text

Test Introduction Algorithm 1. Identify what you need to change. 2. Write characterization test for behavior. 3. Locate test point for unit test. 4. Break dependencies. 5. Write tests. 6. Make changes and refactor.

Slide 18

Slide 18 text

Example 3.

Slide 19

Slide 19 text

The FizzBuzzer Because, really, people will fund anything

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

Anonymous users can run FizzBuzz but only up to to the number 100

Slide 22

Slide 22 text

Whether the job is big or small, do it right or not at all

Slide 23

Slide 23 text

Identify Change Points

Slide 24

Slide 24 text

! ! ! ! ! ! ! FizzBuzz!! !

Result

! !

Slide 25

Slide 25 text

Write Characterization Tests

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"!

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

Refactor and Make Changes

Slide 29

Slide 29 text

! Scenario: ! ! 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"!

Slide 30

Slide 30 text

No content

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']);! }! }!

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,! );! }!

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

+3 feature tests +2 Unit tests Added

Slide 37

Slide 37 text

Freedom to Refactor FEARLESSLY

Slide 38

Slide 38 text

Create Abstractions expose the API you want, guided by tests

Slide 39

Slide 39 text

!

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)! );! }! }

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;! }! }!

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;! }!

Slide 43

Slide 43 text

function fizzbuzz_for_range($start, $end) {! $processor = new RangeProcessor();! return $processor->process($start, $end);! }!

Slide 44

Slide 44 text

Break Dependencies creating seams is your friend

Slide 45

Slide 45 text

namespace Fizzbuzzer;! ! class Order! {! public $id;! ! public function getPayments()! {! $paymentDao = new PaymentDao();! return $paymentDao->getOrderPayments($this->id);! }! }

Slide 46

Slide 46 text

namespace tests\Fizzbuzzer;! ! use Fizzbuzzer\Order;! ! class OrderTest extends \PHPUnit_Framework_TestCase! {! /** @test */! public function shouldReturnPaymentsAppliedToOrder()! {! $o = new Order();! $this->assertNotEmpty($o->getPayments());! }! }!

Slide 47

Slide 47 text

class Order! {! public $id;! ! public function getPayments()! {! $paymentDao = $this->getPaymentDao();! return $paymentDao->getOrderPayments($this->id);! }! ! protected function getPaymentDao()! {! return new PaymentDao();! }! }!

Slide 48

Slide 48 text

class Order! {! public $id;! ! public function getPayments()! {! $paymentDao = $this->getPaymentDao();! return $paymentDao->getOrderPayments($this->id);! }! ! public function setPaymentDao(PaymentDao $paymentDao)! {! $this->paymentDao = $paymentDao;! }! ! protected function getPaymentDao()! {! if (null === $this->paymentDao) {! $this->paymentDao = new PaymentDao();! }! ! return $this->paymentDao;! }! }!

Slide 49

Slide 49 text

class OrderTest extends \PHPUnit_Framework_TestCase! {! /** @test */! public function shouldReturnPaymentsAppliedToOrder()! {! $paymentDao = $this->getMock('\\Fizzbuzzer\\PaymentDao');! $paymentDao->expects($this->any())! ->method('getOrderPayments')! ->will($this->returnValue(array(! $this->getMock('\\Fizzubzzer\\Payment')! )));! ! $o = new Order();! $o->setPaymentDao($paymentDao);! $this->assertNotEmpty($o->getPayments());! }! }!

Slide 50

Slide 50 text

! phpunit tests/OrderTest.php! PHPUnit 3.7.28 by Sebastian Bergmann.! ! Configuration read from /projects/fizzbuzzer/phpunit.xml.dist! ! .! ! Time: 422 ms, Memory: 4.00Mb! ! OK (1 test, 1 assertion)

Slide 51

Slide 51 text

Test Introduction Algorithm 1. Identify what you need to change. 2. Write characterization test for behavior. 3. Locate test point for unit test. 4. Break dependencies. 5. Write tests. 6. Make changes and refactor.

Slide 52

Slide 52 text

Recommended Reading Working Effectively with Legacy Code! by Michael Feathers Modernize Your Legacy PHP Application! by Paul M. Jones! mlaphp.com

Slide 53

Slide 53 text

Thanks. http://speakerdeck.com/jcarouth Q&A Thanks.Q&A https://joind.in/10563 @jcarouth — #mwphp14