Your code are my tests - LoneStarPHP 2014

Preview:

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.

Citation preview

2

Your  code  are  my  testsLoneStarPHP  2014  

Dallas,  TX

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.

3

Michelangelo van Dam!!

PHP Consultant Community Leader

President of PHPBenelux Contributor

Warming  up

4

http

s://w

ww.

flick

r.com

/pho

tos/

bobj

agen

dorf/

8535

3168

36

Smallest  unit  of  code

5

http

s://w

ww.

flick

r.com

/pho

tos/

tool

stop

/454

6017

269

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

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

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

We  don’t  live  in  a  fairy  tale!

9

http

s://w

ww.

flick

r.com

/pho

tos/

bertk

not/8

1752

1490

9

Real  code,  real  apps

10

github.com/Telaxus/EPESI

11

Running  the  project

12

Where  are  the  TESTS?

13

Where  are  the  TESTS?

14

Oh  noes,  no  tests!

15

http

s://w

ww.

flick

r.com

/pho

tos/

mjh

agen

/297

3212

926

Let’s  get  started

16

http

s://w

ww.

flick

r.com

/pho

tos/

npob

re/2

6015

8225

6

How  to  get  about  it?

17

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

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

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

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

Run  test

22

Check  coverage

23

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

Run  tests

25

Check  coverage

26

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

Run  tests

28

Check  code  coverage

29

Non-­‐executable  code

30

http

s://w

ww.

flick

r.com

/pho

tos/

dazj

ohns

on/7

7208

0682

4

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

Run  tests

32

Check  code  coverage

33

Look  at  the  global  coverage

34

Bridging  gaps

35

http

s://w

ww.

flick

r.com

/pho

tos/

hugo

90/6

9807

1264

3

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

/

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

37

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

Don’t  touch  my  junk!

39

http

s://w

ww.

flick

r.com

/pho

tos/

case

ymul

timed

ia/5

4122

9373

0

House  of  Reflec9on

40

http

s://w

ww.

flick

r.com

/pho

tos/

tabo

r-roe

der/8

2507

7011

5

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

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

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

Run  tests

44

Code  Coverage

45

Yes,  paradise  exists

46

http

s://w

ww.

flick

r.com

/pho

tos/

rnug

raha

/200

3147

365

Unit  tes9ng  is  not  difficult!

47

You  just  need  to  get  started

48

PHP  has  all  the  tools

49

And  there  are  more    roads  to  Rome

50

Need  help?

51

Michelangelo van Dam !michelangelo@in2it.be @DragonBe

www.in2it.be

Ques9ons?

52

http

s://w

ww.

flick

r.com

/pho

tos/

mdp

ettit

t/867

1901

426

53

joind.in/10806

Recommended