your code are my tests - lonestarphp 2014

53
2 Your code are my tests LoneStarPHP 2014 Dallas, TX

Upload: michelangelo-van-dam

Post on 29-Jan-2015

109 views

Category:

Engineering


1 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 - LoneStarPHP 2014

2

Your  code  are  my  testsLoneStarPHP  2014  

Dallas,  TX

Page 2: Your code are my tests - LoneStarPHP 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, NOT EVEN IF YOUR NAME IS KEITH CASEY.

Page 3: Your code are my tests - LoneStarPHP 2014

3

Michelangelo van Dam!!

PHP Consultant Community Leader

President of PHPBenelux Contributor

Page 4: Your code are my tests - LoneStarPHP 2014

Warming  up

4

http

s://w

ww.

flick

r.com

/pho

tos/

bobj

agen

dorf/

8535

3168

36

Page 5: Your code are my tests - LoneStarPHP 2014

Smallest  unit  of  code

5

http

s://w

ww.

flick

r.com

/pho

tos/

tool

stop

/454

6017

269

Page 6: Your code are my tests - LoneStarPHP 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; !    } !}

6

Page 7: Your code are my tests - LoneStarPHP 2014

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

7

Page 8: Your code are my tests - LoneStarPHP 2014

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

8

Page 9: Your code are my tests - LoneStarPHP 2014

We  don’t  live  in  a  fairy  tale!

9

http

s://w

ww.

flick

r.com

/pho

tos/

bertk

not/8

1752

1490

9

Page 10: Your code are my tests - LoneStarPHP 2014

Real  code,  real  apps

10

Page 11: Your code are my tests - LoneStarPHP 2014

github.com/Telaxus/EPESI

11

Page 12: Your code are my tests - LoneStarPHP 2014

Running  the  project

12

Page 13: Your code are my tests - LoneStarPHP 2014

Where  are  the  TESTS?

13

Page 14: Your code are my tests - LoneStarPHP 2014

Where  are  the  TESTS?

14

Page 15: Your code are my tests - LoneStarPHP 2014

Oh  noes,  no  tests!

15

http

s://w

ww.

flick

r.com

/pho

tos/

mjh

agen

/297

3212

926

Page 16: Your code are my tests - LoneStarPHP 2014

Let’s  get  started

16

http

s://w

ww.

flick

r.com

/pho

tos/

npob

re/2

6015

8225

6

Page 17: Your code are my tests - LoneStarPHP 2014

How  to  get  about  it?

17

Page 18: Your code are my tests - LoneStarPHP 2014

SeKng  up  for  tes9ng<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>

18

Page 19: Your code are my tests - LoneStarPHP 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  • saBsfy_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

19

Page 20: Your code are my tests - LoneStarPHP 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; !}

20

Page 21: Your code are my tests - LoneStarPHP 2014

Tes9ng  first  condi9on<?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'); !    } !}

21

Page 22: Your code are my tests - LoneStarPHP 2014

Run  test

22

Page 23: Your code are my tests - LoneStarPHP 2014

Check  coverage

23

Page 24: Your code are my tests - LoneStarPHP 2014

Test  for  second  condi9onpublic 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'); !}

24

Page 25: Your code are my tests - LoneStarPHP 2014

Run  tests

25

Page 26: Your code are my tests - LoneStarPHP 2014

Check  coverage

26

Page 27: Your code are my tests - LoneStarPHP 2014

Test  for  third  condi9onpublic 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'); !}

27

Page 28: Your code are my tests - LoneStarPHP 2014

Run  tests

28

Page 29: Your code are my tests - LoneStarPHP 2014

Check  code  coverage

29

Page 30: Your code are my tests - LoneStarPHP 2014

Non-­‐executable  code

30

http

s://w

ww.

flick

r.com

/pho

tos/

dazj

ohns

on/7

7208

0682

4

Page 31: Your code are my tests - LoneStarPHP 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'); !}

31

Page 32: Your code are my tests - LoneStarPHP 2014

Run  tests

32

Page 33: Your code are my tests - LoneStarPHP 2014

Check  code  coverage

33

Page 34: Your code are my tests - LoneStarPHP 2014

Look  at  the  global  coverage

34

Page 35: Your code are my tests - LoneStarPHP 2014

Bridging  gaps

35

http

s://w

ww.

flick

r.com

/pho

tos/

hugo

90/6

9807

1264

3

Page 36: Your code are my tests - LoneStarPHP 2014

Privates  exposed

36 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 37: Your code are my tests - LoneStarPHP 2014

Dependency• __construct  • get_module_name  • get_version_min  • get_version_max  • is_saBsfied_by  • requires  • requires_exact  • requires_at_least  • requires_range

37

Page 38: Your code are my tests - LoneStarPHP 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 ? '<=' : '<'; !    } !!    /** ... */ !}

38

Page 39: Your code are my tests - LoneStarPHP 2014

Don’t  touch  my  junk!

39

http

s://w

ww.

flick

r.com

/pho

tos/

case

ymul

timed

ia/5

4122

9373

0

Page 40: Your code are my tests - LoneStarPHP 2014

House  of  Reflec9on

40

http

s://w

ww.

flick

r.com

/pho

tos/

tabo

r-roe

der/8

2507

7011

5

Page 41: Your code are my tests - LoneStarPHP 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!!    } !}

41

Page 42: Your code are my tests - LoneStarPHP 2014

Let’s  use  the  sta9c$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');

42

Page 43: Your code are my tests - LoneStarPHP 2014

Use  the  reflec9on  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');

43

Page 44: Your code are my tests - LoneStarPHP 2014

Run  tests

44

Page 45: Your code are my tests - LoneStarPHP 2014

Code  Coverage

45

Page 46: Your code are my tests - LoneStarPHP 2014

Yes,  paradise  exists

46

http

s://w

ww.

flick

r.com

/pho

tos/

rnug

raha

/200

3147

365

Page 47: Your code are my tests - LoneStarPHP 2014

Unit  tes9ng  is  not  difficult!

47

Page 48: Your code are my tests - LoneStarPHP 2014

You  just  need  to  get  started

48

Page 49: Your code are my tests - LoneStarPHP 2014

PHP  has  all  the  tools

49

Page 50: Your code are my tests - LoneStarPHP 2014

And  there  are  more    roads  to  Rome

50

Page 51: Your code are my tests - LoneStarPHP 2014

Need  help?

51

Michelangelo van Dam [email protected] @DragonBe

www.in2it.be

Page 52: Your code are my tests - LoneStarPHP 2014

Ques9ons?

52

http

s://w

ww.

flick

r.com

/pho

tos/

mdp

ettit

t/867

1901

426

Page 53: Your code are my tests - LoneStarPHP 2014

53

joind.in/10806