testing legacy code - phpbenelux meetup x-com

71
2 Tes$ng Legacy Code PHPBenelux Meetup 2014 XCom Venlo (NL)

Upload: michelangelo-van-dam

Post on 12-Jul-2015

354 views

Category:

Engineering


0 download

TRANSCRIPT

Page 1: Testing legacy code - PHPBenelux Meetup X-Com

2

Tes$ng  Legacy  CodePHPBenelux  Meetup  2014  X-­‐Com  Venlo  (NL)

Page 2: Testing legacy code - PHPBenelux Meetup X-Com

2

ADVISORYIN ORDER TO EXPLAIN CERTAIN SITUATIONS YOU MIGHT FACE IN YOUR DEVELOPMENT CAREER, WE WILL BE DISCUSSING THE USAGE OF PRIVATES AND PUBLIC EXPOSURE. IF THESE TOPICS OFFEND OR UPSET YOU, WE WOULD LIKE TO ASK YOU TO LEAVE THIS ROOM NOW. !THE SPEAKER NOR THE ORGANISATION CANNOT BE HELD ACCOUNTABLE FOR MENTAL DISTRESS OR ANY FORMS OF DAMAGE YOU MIGHT ENDURE DURING OR AFTER THIS PRESENTATION. FOR COMPLAINTS PLEASE INFORM ORGANISATION AT PHPBENELUX.

Page 3: Testing legacy code - PHPBenelux Meetup X-Com

3

Michelangelo van Dam!!

PHP Consultant Community Leader

President of PHPBenelux Contributor

Page 4: Testing legacy code - PHPBenelux Meetup X-Com

Why  bother  with  tes$ng?

4

http

s://w

ww.

flick

r.com

/pho

tos/

vial

bost

/553

3266

530

Page 5: Testing legacy code - PHPBenelux Meetup X-Com

Reasons  why  not  to  test

• No  <me  • No  budget  • We  deliver  tests  aFer  delivery  (  this  means  never  )  • We  don’t  know  how…

5

Page 6: Testing legacy code - PHPBenelux Meetup X-Com

No  excuses!!!

6

Crea<ve  Co

mmon

s  -­‐  hMp://www.flickr.com

/pho

tos/akrabat/8421560178

Page 7: Testing legacy code - PHPBenelux Meetup X-Com

Responsibility  issue

• As  a  developer,  it’s  your  job  to  • write  code  &  fixing  bugs  • add  documenta<on  • write  &  update  unit  tests

7

Page 8: Testing legacy code - PHPBenelux Meetup X-Com

Pizza  principle

8

Topping:  your  tests

Box:  your  documenta<on

Dough:  your  code

Page 9: Testing legacy code - PHPBenelux Meetup X-Com

Benefits  of  tes$ng

• Direct  feedback  (test  fails)  • Once  a  test  is  made,  it  will  always  be  tested  • Easy  to  refactor  exis<ng  code  (protec<on)  • Easy  to  debug:  write  a  test  to  see  if  a  bug  is  genuine  • Higher  confidence  and  less  uncertainty

9

Page 10: Testing legacy code - PHPBenelux Meetup X-Com

Rule  of  thumb

“Whenever   you   are   tempted   to   type   something   into   a   print  statement  or  a  debugger  expression,  write  it  as  a  test  instead.”  !

—  Source:  Mar?n  Fowler

10

Page 11: Testing legacy code - PHPBenelux Meetup X-Com

Warming  up

11

http

s://w

ww.

flick

r.com

/pho

tos/

bobj

agen

dorf/

8535

3168

36

Page 12: Testing legacy code - PHPBenelux Meetup X-Com

PHPUnit

• PHPUnit  is  a  port  of  xUnit  tes<ng  framework  • Created  by  “Sebas<an  Bergmann”  • Uses  “asser<ons”  to  verify  behaviour  of  “unit  of  code”  • Open  source  and  hosted  on  GitHub  

• See  hMps://github.com/sebas<anbergmann/phpunit  • Can  be  installed  using:  

• PEAR  • PHAR  • Composer

12

Page 13: Testing legacy code - PHPBenelux Meetup X-Com

Approach  for  tes$ng

• Instan<ate  a  “unit-­‐of-­‐code”  • Assert  expected  result  against  actual  result  • Provide  a  custom  error  message

13

Page 14: Testing legacy code - PHPBenelux Meetup X-Com

Available  asser$ons• assertArrayHasKey()  • assertClassHasAMribute()  • assertClassHasSta<cAMribute()  • assertContains()  • assertContainsOnly()  • assertContainsOnlyInstancesOf()  • assertCount()  • assertEmpty()  • assertEqualXMLStructure()  • assertEquals()  • assertFalse()  • assertFileEquals()  • assertFileExists()  • assertGreaterThan()  • assertGreaterThanOrEqual()  • assertInstanceOf()  • assertInternalType()  • assertJsonFileEqualsJsonFile()  • assertJsonStringEqualsJsonFile()  • assertJsonStringEqualsJsonString()  

• assertLessThan()  • assertLessThanOrEqual()  • assertNull()  • assertObjectHasAMribute()  • assertRegExp()  • assertStringMatchesFormat()  • assertStringMatchesFormatFile()  • assertSame()  • assertSelectCount()  • assertSelectEquals()  • assertSelectRegExp()  • assertStringEndsWith()  • assertStringEqualsFile()  • assertStringStartsWith()  • assertTag()  • assertThat()  • assertTrue()  • assertXmlFileEqualsXmlFile()  • assertXmlStringEqualsXmlFile()  • assertXmlStringEqualsXmlString()

14

Page 15: Testing legacy code - PHPBenelux Meetup X-Com

To  protect  and  to  serve

15

Page 16: Testing legacy code - PHPBenelux Meetup X-Com

Data  is  tainted,  ALWAYS

16

HackersBAD DATA

Web Services

Stupid users

Page 19: Testing legacy code - PHPBenelux Meetup X-Com

OWASP  top  10  exploits

19https://www.owasp.org/index.php/Top_10_2013-Top_10

Page 20: Testing legacy code - PHPBenelux Meetup X-Com

Filtering  &  Valida$on

20

Page 21: Testing legacy code - PHPBenelux Meetup X-Com

Smallest  unit  of  code

21

http

s://w

ww.

flick

r.com

/pho

tos/

tool

stop

/454

6017

269

Page 22: Testing legacy code - PHPBenelux Meetup X-Com

Example  class<?php !/** ! * Example class ! */ !class MyClass !{ !    /** ... */ !    public function doSomething($requiredParam, $optionalParam = null) !    { !        if (!filter_var( !            $requiredParam, FILTER_SANITIZE_STRING, FILTER_FLAG_ENCODE_HIGH !        )) { !            throw new InvalidArgumentException('Invalid argument provided'); !        } !        if (null !== $optionalParam) { !            if (!filter_var( !                $optionalParam, FILTER_SANITIZE_STRING, FILTER_FLAG_ENCODE_HIGH !            )) { !                throw new InvalidArgumentException('Invalid argument provided'); !            } !            $requiredParam .= ' - ' . $optionalParam; !        } !        return $requiredParam; !    } !}

22

Page 23: Testing legacy code - PHPBenelux Meetup X-Com

Tes$ng  for  good   /** ... */!    public function testClassAcceptsValidRequiredArgument() !    { !        $expected = $argument = 'Testing PHP Class'; !        $myClass = new MyClass; !        $result = $myClass->doSomething($argument); !        $this->assertSame($expected, $result,  !            'Expected result differs from actual result'); !    } !!   /** ... */     !    public function testClassAcceptsValidOptionalArgument() !    { !        $requiredArgument = 'Testing PHP Class'; !        $optionalArgument = 'Is this not fun?!?'; !        $expected = $requiredArgument . ' - ' . $optionalArgument; !        $myClass = new MyClass; !        $result = $myClass->doSomething($requiredArgument, $optionalArgument); !        $this->assertSame($expected, $result,  !            'Expected result differs from actual result'); !    }

23

Page 24: Testing legacy code - PHPBenelux Meetup X-Com

Tes$ng  for  bad    /** !     * @expectedException InvalidArgumentException !     */ !    public function testExceptionIsThrownForInvalidRequiredArgument() !    { !        $expected = $argument = new StdClass; !        $myClass = new MyClass; !        $result = $myClass->doSomething($argument); !        $this->assertSame($expected, $result,  !            'Expected result differs from actual result'); !    } !     !    /** !     * @expectedException InvalidArgumentException !     */ !    public function testExceptionIsThrownForInvalidOptionalArgument() !    { !        $requiredArgument = 'Testing PHP Class'; !        $optionalArgument = new StdClass; !        $myClass = new MyClass; !        $result = $myClass->doSomething($requiredArgument, $optionalArgument); !        $this->assertSame($expected, $result,  !            'Expected result differs from actual result'); !    }

24

Page 25: Testing legacy code - PHPBenelux Meetup X-Com

Example:  tes$ng  payments<?php  namespace  Myapp\Common\Payment;      class  ProcessTest  extends  \PHPUnit_Framework_TestCase  {          public  function  testPaymentIsProcessedCorrectly()          {                  $customer  =  new  Customer(/*  data  for  customer  */);                  $transaction  =  new  Transaction(/*  data  for  transaction  */);                  $process  =  new  Process('sale',  $customer,  $transaction);                  $process-­‐>pay();                      $this-­‐>assertTrue($process-­‐>paymentApproved());                  $this-­‐>assertEquals('PAY-­‐17S8410768582940NKEE66EQ',  $process-­‐>getPaymentId());          }  }

25

Page 26: Testing legacy code - PHPBenelux Meetup X-Com

We  don’t  live  in  a  fairy  tale!

26

http

s://w

ww.

flick

r.com

/pho

tos/

bertk

not/8

1752

1490

9

Page 27: Testing legacy code - PHPBenelux Meetup X-Com

Real  code,  real  apps

27

Page 28: Testing legacy code - PHPBenelux Meetup X-Com

github.com/Telaxus/EPESI

28

Page 29: Testing legacy code - PHPBenelux Meetup X-Com

Running  the  project

29

Page 30: Testing legacy code - PHPBenelux Meetup X-Com

Where  are  the  TESTS?

30

Page 31: Testing legacy code - PHPBenelux Meetup X-Com

Where  are  the  TESTS?

31

Page 32: Testing legacy code - PHPBenelux Meetup X-Com

Oh  noes,  no  tests!

32

http

s://w

ww.

flick

r.com

/pho

tos/

mjh

agen

/297

3212

926

Page 33: Testing legacy code - PHPBenelux Meetup X-Com

Let’s  get  started

33

http

s://w

ww.

flick

r.com

/pho

tos/

npob

re/2

6015

8225

6

Page 34: Testing legacy code - PHPBenelux Meetup X-Com

How  to  get  about  it?

34

Page 35: Testing legacy code - PHPBenelux Meetup X-Com

Se]ng  up  for  tes$ng<phpunit colors="true" stopOnError="true" stopOnFailure="true">! <testsuites>! <testsuite name="EPESI admin tests">! <directory phpVersion="5.3.0">tests/admin</directory>! </testsuite>! <testsuite name="EPESI include tests">! <directory phpVersion="5.3.0">tests/include</directory>! </testsuite>! <testsuite name="EPESI modules testsuite">! <directory phpVersion="5.3.0">tests/modules</directory>! </testsuite>! </testsuites>! <php>! <const name="DEBUG_AUTOLOADS" value="1"/>! <const name="CID" value="1234567890123456789"/>! </php>! <logging>! <log type="coverage-html" target="build/coverage" charset="UTF-8"/>! <log type="coverage-clover" target="build/logs/clover.xml"/>! <log type="junit" target="build/logs/junit.xml"/>! </logging>!</phpunit>

35

Page 36: Testing legacy code - PHPBenelux Meetup X-Com

ModuleManager• not_loaded_modules  • loaded_modules  • modules  • modules_install  • modules_common  • root  • processing  • processed_modules  • include_install  • include_common  • include_main  • create_load_priority_array  • check_dependencies  • sa<sfy_dependencies  • get_module_dir_path  • get_module_file_name  • list_modules  • exists  • register  • unregister  • is_installed  

• upgrade  • downgrade  • get_module_class_name  • install  • uninstall  • get_processed_modules  • get_load_priority_array  • new_instance  • get_instance  • create_data_dir  • remove_data_dir  • get_data_dir  • load_modules  • create_common_cache  • create_root  • check_access  • call_common_methods  • check_common_methods  • required_modules  • reset_cron

36

Page 37: Testing legacy code - PHPBenelux Meetup X-Com

ModuleManager::module_install/** ! * Includes file with module installation class. ! * ! * Do not use directly. ! * ! * @param string $module_class_name module class name - underscore separated ! */ !public static final function include_install($module_class_name) { !    if(isset(self::$modules_install[$module_class_name])) return true; !    $path = self::get_module_dir_path($module_class_name); !    $file = self::get_module_file_name($module_class_name); !    $full_path = 'modules/' . $path . '/' . $file . 'Install.php'; !    if (!file_exists($full_path)) return false; !    ob_start(); !    $ret = require_once($full_path); !    ob_end_clean(); !    $x = $module_class_name.'Install'; !    if(!(class_exists($x, false)) || ! !array_key_exists('ModuleInstall',class_parents($x))) !        trigger_error('Module '.$path.': Invalid install file',E_USER_ERROR); !    self::$modules_install[$module_class_name] = new $x($module_class_name); !    return true; !}

37

Page 38: Testing legacy code - PHPBenelux Meetup X-Com

Tes$ng  first  condi$on<?php !!require_once 'include.php'; !!class ModuleManagerTest extends PHPUnit_Framework_TestCase !{ !    protected function tearDown() !    { !        ModuleManager::$modules_install = array (); !    } !!    public function testReturnImmediatelyWhenModuleAlreadyLoaded() !    { !        $module = 'Foo_Bar'; !        ModuleManager::$modules_install[$module] = 1; !        $result = ModuleManager::include_install($module); !        $this->assertTrue($result, !            'Expecting that an already installed module returns true'); !        $this->assertCount(1, ModuleManager::$modules_install, !            'Expecting to find 1 module ready for installation'); !    } !}

38

Page 39: Testing legacy code - PHPBenelux Meetup X-Com

Run  test

39

Page 40: Testing legacy code - PHPBenelux Meetup X-Com

Check  coverage

40

Page 41: Testing legacy code - PHPBenelux Meetup X-Com

Test  for  second  condi$onpublic function testLoadingNonExistingModuleIsNotExecuted() !{ !    $module = 'Foo_Bar'; !    $result = ModuleManager::include_install($module); !    $this->assertFalse($result, 'Expecting failure for loading Foo_Bar'); !    $this->assertEmpty(ModuleManager::$modules_install, !        'Expecting to find no modules ready for installation'); !}

41

Page 42: Testing legacy code - PHPBenelux Meetup X-Com

Run  tests

42

Page 43: Testing legacy code - PHPBenelux Meetup X-Com

Check  coverage

43

Page 44: Testing legacy code - PHPBenelux Meetup X-Com

Test  for  third  condi$onpublic function testNoInstallationOfModuleWithoutInstallationClass() !{ !    $module = 'EssClient_IClient'; !    $result = ModuleManager::include_install($module); !    $this->assertFalse($result, 'Expecting failure for loading Foo_Bar'); !    $this->assertEmpty(ModuleManager::$modules_install, !        'Expecting to find no modules ready for installation'); !}

44

Page 45: Testing legacy code - PHPBenelux Meetup X-Com

Run  tests

45

Page 46: Testing legacy code - PHPBenelux Meetup X-Com

Check  code  coverage

46

Page 47: Testing legacy code - PHPBenelux Meetup X-Com

Non-­‐executable  code

47

http

s://w

ww.

flick

r.com

/pho

tos/

dazj

ohns

on/7

7208

0682

4

Page 48: Testing legacy code - PHPBenelux Meetup X-Com

Test  for  successpublic function testIncludeClassFileForLoadingModule() !{ !    $module = 'Base_About'; !    $result = ModuleManager::include_install($module); !    $this->assertTrue($result, 'Expected module to be loaded'); !    $this->assertCount(1, ModuleManager::$modules_install, !        'Expecting to find 1 module ready for installation'); !}

48

Page 49: Testing legacy code - PHPBenelux Meetup X-Com

Run  tests

49

Page 50: Testing legacy code - PHPBenelux Meetup X-Com

Check  code  coverage

50

Page 51: Testing legacy code - PHPBenelux Meetup X-Com

Look  at  the  global  coverage

51

Page 52: Testing legacy code - PHPBenelux Meetup X-Com

Bridging  gaps

52

http

s://w

ww.

flick

r.com

/pho

tos/

hugo

90/6

9807

1264

3

Page 53: Testing legacy code - PHPBenelux Meetup X-Com

Privates  exposed

53 http

://w

ww.

slas

hgea

r.com

/form

er-ts

a-ag

ent-a

dmits

-we-

knew

-full-

body

-sca

nner

s-di

dnt-w

ork-

3131

5288

/

Page 54: Testing legacy code - PHPBenelux Meetup X-Com

Dependency• __construct  • get_module_name  • get_version_min  • get_version_max  • is_sa<sfied_by  • requires  • requires_exact  • requires_at_least  • requires_range

54

Page 55: Testing legacy code - PHPBenelux Meetup X-Com

A  private  constructor!<?php !!defined("_VALID_ACCESS") || die('Direct access forbidden'); !!/** ! * This class provides dependency requirements ! * @package epesi-base ! * @subpackage module  ! */ !class Dependency { !!    private $module_name; !    private $version_min; !    private $version_max; !    private $compare_max; !!    private function __construct(! $module_name, $version_min, $version_max, $version_max_is_ok = true) { !        $this->module_name = $module_name; !        $this->version_min = $version_min; !        $this->version_max = $version_max; !        $this->compare_max = $version_max_is_ok ? '<=' : '<'; !    } !!    /** ... */ !}

55

Page 56: Testing legacy code - PHPBenelux Meetup X-Com

Don’t  touch  my  junk!

56

http

s://w

ww.

flick

r.com

/pho

tos/

case

ymul

timed

ia/5

4122

9373

0

Page 57: Testing legacy code - PHPBenelux Meetup X-Com

House  of  Reflec$on

57

http

s://w

ww.

flick

r.com

/pho

tos/

tabo

r-roe

der/8

2507

7011

5

Page 58: Testing legacy code - PHPBenelux Meetup X-Com

Let’s  do  this…<?php !require_once 'include.php'; !!class DependencyTest extends PHPUnit_Framework_TestCase !{ !    public function testConstructorSetsProperSettings() !    { !        require_once 'include/module_dependency.php'; !!        // We have a problem, the constructor is private!!    } !}

58

Page 59: Testing legacy code - PHPBenelux Meetup X-Com

Let’s  use  the  sta$c$params = array ( !    'moduleName' => 'Foo_Bar', !    'minVersion' => 0, !    'maxVersion' => 1, !    'maxOk' => true, !); !// We use a static method for this test !$dependency = Dependency::requires_range( !    $params['moduleName'], !    $params['minVersion'], !    $params['maxVersion'], !    $params['maxOk'] !); !!// We use reflection to see if properties are set correctly !$reflectionClass = new ReflectionClass('Dependency');

59

Page 60: Testing legacy code - PHPBenelux Meetup X-Com

Use  the  reflec$on  to  assert// Let's retrieve the private properties !$moduleName = $reflectionClass->getProperty('module_name'); !$moduleName->setAccessible(true); !$minVersion = $reflectionClass->getProperty('version_min'); !$minVersion->setAccessible(true); !$maxVersion = $reflectionClass->getProperty('version_max'); !$maxVersion->setAccessible(true); !$maxOk = $reflectionClass->getProperty('compare_max'); !$maxOk->setAccessible(true); !!// Let's assert !$this->assertEquals($params['moduleName'], $moduleName->getValue($dependency), !    'Expected value does not match the value set’);! !$this->assertEquals($params['minVersion'], $minVersion->getValue($dependency), !    'Expected value does not match the value set’);! !$this->assertEquals($params['maxVersion'], $maxVersion->getValue($dependency), !    'Expected value does not match the value set’);! !$this->assertEquals('<=', $maxOk->getValue($dependency), !    'Expected value does not match the value set');

60

Page 61: Testing legacy code - PHPBenelux Meetup X-Com

Run  tests

61

Page 62: Testing legacy code - PHPBenelux Meetup X-Com

Code  Coverage

62

Page 63: Testing legacy code - PHPBenelux Meetup X-Com

Yes,  paradise  exists

63

http

s://w

ww.

flick

r.com

/pho

tos/

rnug

raha

/200

3147

365

Page 64: Testing legacy code - PHPBenelux Meetup X-Com

Unit  tes$ng  is  not  difficult!

64

Page 65: Testing legacy code - PHPBenelux Meetup X-Com

You  just  need  to  get  started

65

Page 66: Testing legacy code - PHPBenelux Meetup X-Com

PHP  has  all  the  tools

66

Page 67: Testing legacy code - PHPBenelux Meetup X-Com

And  there  are  more    roads  to  Rome

67

Page 69: Testing legacy code - PHPBenelux Meetup X-Com

69

joind.in/13089!!

Slides are on joindin While you're there give some feedback

!If you liked my talk, thanks.

If not, let me know how to improve it

Page 70: Testing legacy code - PHPBenelux Meetup X-Com

Ques$ons?

70

http

s://w

ww.

flick

r.com

/pho

tos/

mdp

ettit

t/867

1901

426

Page 71: Testing legacy code - PHPBenelux Meetup X-Com

Need  help?

71

Michelangelo van Dam [email protected] @DragonBe

www.in2it.be