your code are my tests - phpcon poland 2014

71
2 Your code are my tests

Upload: michelangelo-van-dam

Post on 27-Nov-2014

236 views

Category:

Technology


0 download

DESCRIPTION

After years of promoting PHPUnit I still hear it's hard to get started with unit testing. So instead of showing nice step-by-step examples on how to use PHPUnit, we're going to take examples straight from github and start writing tests for them where I explain the issue, how we test it and how we finally got things tested. So I take on the challenge to start writing tests for PHP projects that already have unit tests in place (like major frameworks) and projects that not even started with unit testing.

TRANSCRIPT

Page 1: Your code are my tests - PHPCon Poland 2014

2

Your  code  are  my  tests

Page 2: Your code are my tests - PHPCon Poland 2014

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 [email protected].

Page 3: Your code are my tests - PHPCon Poland 2014

3

Michelangelo van Dam!!

PHP Consultant Community Leader

President of PHPBenelux Contributor

Page 4: Your code are my tests - PHPCon Poland 2014

Why  bother  with  tes3ng?

4

http

s://w

ww.

flick

r.com

/pho

tos/

vial

bost

/553

3266

530

Page 5: Your code are my tests - PHPCon Poland 2014

Reasons  why  not  to  test

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

5

Page 6: Your code are my tests - PHPCon Poland 2014

No  excuses!!!

6

Crea%ve  Co

mmon

s  -­‐  h@p://www.flickr.com

/pho

tos/akrabat/8421560178

Page 7: Your code are my tests - PHPCon Poland 2014

Responsibility  issue

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

7

Page 8: Your code are my tests - PHPCon Poland 2014

Pizza  principle

8

Topping:  your  tests

Box:  your  documenta%on

Dough:  your  code

Page 9: Your code are my tests - PHPCon Poland 2014

Benefits  of  tes3ng

• 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: Your code are my tests - PHPCon Poland 2014

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: Your code are my tests - PHPCon Poland 2014

Warming  up

11

http

s://w

ww.

flick

r.com

/pho

tos/

bobj

agen

dorf/

8535

3168

36

Page 12: Your code are my tests - PHPCon Poland 2014

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  h@ps://github.com/sebas%anbergmann/phpunit  • Can  be  installed  using:  

• PEAR  • PHAR  • Composer

12

Page 13: Your code are my tests - PHPCon Poland 2014

Approach  for  tes3ng

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

13

Page 14: Your code are my tests - PHPCon Poland 2014

Available  asser3ons• assertArrayHasKey()  • assertClassHasA@ribute()  • assertClassHasSta%cA@ribute()  • assertContains()  • assertContainsOnly()  • assertContainsOnlyInstancesOf()  • assertCount()  • assertEmpty()  • assertEqualXMLStructure()  • assertEquals()  • assertFalse()  • assertFileEquals()  • assertFileExists()  • assertGreaterThan()  • assertGreaterThanOrEqual()  • assertInstanceOf()  • assertInternalType()  • assertJsonFileEqualsJsonFile()  • assertJsonStringEqualsJsonFile()  • assertJsonStringEqualsJsonString()  

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

14

Page 15: Your code are my tests - PHPCon Poland 2014

To  protect  and  to  serve

15

Page 16: Your code are my tests - PHPCon Poland 2014

Data  is  tainted,  ALWAYS

16

Hackers BAD DATA

Web S

ervicesStupid users

Page 19: Your code are my tests - PHPCon Poland 2014

OWASP  top  10  exploits

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

Page 20: Your code are my tests - PHPCon Poland 2014

Filtering  &  Valida3on

20

Page 21: Your code are my tests - PHPCon Poland 2014

Smallest  unit  of  code

21

http

s://w

ww.

flick

r.com

/pho

tos/

tool

stop

/454

6017

269

Page 22: Your code are my tests - PHPCon Poland 2014

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: Your code are my tests - PHPCon Poland 2014

Tes3ng  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: Your code are my tests - PHPCon Poland 2014

Tes3ng  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: Your code are my tests - PHPCon Poland 2014

Example:  tes3ng  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: Your code are my tests - PHPCon Poland 2014

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: Your code are my tests - PHPCon Poland 2014

Real  code,  real  apps

27

Page 28: Your code are my tests - PHPCon Poland 2014

github.com/Telaxus/EPESI

28

Page 29: Your code are my tests - PHPCon Poland 2014

Running  the  project

29

Page 30: Your code are my tests - PHPCon Poland 2014

Where  are  the  TESTS?

30

Page 31: Your code are my tests - PHPCon Poland 2014

Where  are  the  TESTS?

31

Page 32: Your code are my tests - PHPCon Poland 2014

Oh  noes,  no  tests!

32

http

s://w

ww.

flick

r.com

/pho

tos/

mjh

agen

/297

3212

926

Page 33: Your code are my tests - PHPCon Poland 2014

Let’s  get  started

33

http

s://w

ww.

flick

r.com

/pho

tos/

npob

re/2

6015

8225

6

Page 34: Your code are my tests - PHPCon Poland 2014

How  to  get  about  it?

34

Page 35: Your code are my tests - PHPCon Poland 2014

Se]ng  up  for  tes3ng<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: Your code are my tests - PHPCon Poland 2014

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: Your code are my tests - PHPCon Poland 2014

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: Your code are my tests - PHPCon Poland 2014

Tes3ng  first  condi3on<?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: Your code are my tests - PHPCon Poland 2014

Run  test

39

Page 40: Your code are my tests - PHPCon Poland 2014

Check  coverage

40

Page 41: Your code are my tests - PHPCon Poland 2014

Test  for  second  condi3onpublic 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: Your code are my tests - PHPCon Poland 2014

Run  tests

42

Page 43: Your code are my tests - PHPCon Poland 2014

Check  coverage

43

Page 44: Your code are my tests - PHPCon Poland 2014

Test  for  third  condi3onpublic 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: Your code are my tests - PHPCon Poland 2014

Run  tests

45

Page 46: Your code are my tests - PHPCon Poland 2014

Check  code  coverage

46

Page 47: Your code are my tests - PHPCon Poland 2014

Non-­‐executable  code

47

http

s://w

ww.

flick

r.com

/pho

tos/

dazj

ohns

on/7

7208

0682

4

Page 48: Your code are my tests - PHPCon Poland 2014

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: Your code are my tests - PHPCon Poland 2014

Run  tests

49

Page 50: Your code are my tests - PHPCon Poland 2014

Check  code  coverage

50

Page 51: Your code are my tests - PHPCon Poland 2014

Look  at  the  global  coverage

51

Page 52: Your code are my tests - PHPCon Poland 2014

Bridging  gaps

52

http

s://w

ww.

flick

r.com

/pho

tos/

hugo

90/6

9807

1264

3

Page 53: Your code are my tests - PHPCon Poland 2014

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: Your code are my tests - PHPCon Poland 2014

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: Your code are my tests - PHPCon Poland 2014

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: Your code are my tests - PHPCon Poland 2014

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: Your code are my tests - PHPCon Poland 2014

House  of  Reflec3on

57

http

s://w

ww.

flick

r.com

/pho

tos/

tabo

r-roe

der/8

2507

7011

5

Page 58: Your code are my tests - PHPCon Poland 2014

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: Your code are my tests - PHPCon Poland 2014

Let’s  use  the  sta3c$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: Your code are my tests - PHPCon Poland 2014

Use  the  reflec3on  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: Your code are my tests - PHPCon Poland 2014

Run  tests

61

Page 62: Your code are my tests - PHPCon Poland 2014

Code  Coverage

62

Page 63: Your code are my tests - PHPCon Poland 2014

Yes,  paradise  exists

63

http

s://w

ww.

flick

r.com

/pho

tos/

rnug

raha

/200

3147

365

Page 64: Your code are my tests - PHPCon Poland 2014

Unit  tes3ng  is  not  difficult!

64

Page 65: Your code are my tests - PHPCon Poland 2014

You  just  need  to  get  started

65

Page 66: Your code are my tests - PHPCon Poland 2014

PHP  has  all  the  tools

66

Page 67: Your code are my tests - PHPCon Poland 2014

And  there  are  more    roads  to  Rome

67

Page 69: Your code are my tests - PHPCon Poland 2014

Need  help?

69

Michelangelo van Dam [email protected] @DragonBe

www.in2it.be

Page 70: Your code are my tests - PHPCon Poland 2014

Ques3ons?

70

http

s://w

ww.

flick

r.com

/pho

tos/

mdp

ettit

t/867

1901

426

Page 71: Your code are my tests - PHPCon Poland 2014

71

joind.in/11840!!

If you liked my talk, thanks. If not, let me know how to improve it