your code are my tests - php serbia testday 2014

Post on 02-Jul-2015

1.257 Views

Category:

Engineering

1 Downloads

Preview:

Click to see full reader

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 an example straight from github. So I've taken the challenge to start writing tests for PHP projects that don't have unit tests in place and explain how I decide where to begin, how I approach my test strategy and how I ensure I’m covering each possible use-case (and covering the CRAP index). The goal of this presentation is to show everyone that even legacy code, spaghetti code and complex code bases can be tested. After this talk you can immediately apply my examples on your own codebase (even if it's a clean code base) and get started with testing. To follow along a basic knowledge unit testing with PHPUnit is required.

TRANSCRIPT

2

Your  code  are  my  tests

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 INFO@PHPCON.PL.

3

Michelangelo van Dam!!

PHP Consultant Community Leader

President of PHPBenelux Contributor

Why  bother  with  tes3ng?

4

http

s://w

ww.

flick

r.com

/pho

tos/

vial

bost

/553

3266

530

Reasons  why  not  to  test

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

5

No  excuses!!!

6

Crea%ve  Co

mmon

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

/pho

tos/akrabat/8421560178

Responsibility  issue

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

7

Pizza  principle

8

Topping:  your  tests

Box:  your  documenta%on

Dough:  your  code

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

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

Warming  up

11

http

s://w

ww.

flick

r.com

/pho

tos/

bobj

agen

dorf/

8535

3168

36

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

Approach  for  tes3ng

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

13

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

To  protect  and  to  serve

15

Data  is  tainted,  ALWAYS

16

HackersBAD DATA

Web Services

Stupid users

OWASP  top  10  exploits

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

Filtering  &  Valida3on

20

Smallest  unit  of  code

21

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

22

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

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

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

We  don’t  live  in  a  fairy  tale!

26

http

s://w

ww.

flick

r.com

/pho

tos/

bertk

not/8

1752

1490

9

Real  code,  real  apps

27

github.com/Telaxus/EPESI

28

Running  the  project

29

Where  are  the  TESTS?

30

Where  are  the  TESTS?

31

Oh  noes,  no  tests!

32

http

s://w

ww.

flick

r.com

/pho

tos/

mjh

agen

/297

3212

926

Let’s  get  started

33

http

s://w

ww.

flick

r.com

/pho

tos/

npob

re/2

6015

8225

6

How  to  get  about  it?

34

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

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

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

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

Run  test

39

Check  coverage

40

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

Run  tests

42

Check  coverage

43

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

Run  tests

45

Check  code  coverage

46

Non-­‐executable  code

47

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

48

Run  tests

49

Check  code  coverage

50

Look  at  the  global  coverage

51

Bridging  gaps

52

http

s://w

ww.

flick

r.com

/pho

tos/

hugo

90/6

9807

1264

3

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

/

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

54

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

Don’t  touch  my  junk!

56

http

s://w

ww.

flick

r.com

/pho

tos/

case

ymul

timed

ia/5

4122

9373

0

House  of  Reflec3on

57

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

58

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

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

Run  tests

61

Code  Coverage

62

Yes,  paradise  exists

63

http

s://w

ww.

flick

r.com

/pho

tos/

rnug

raha

/200

3147

365

Unit  tes3ng  is  not  difficult!

64

You  just  need  to  get  started

65

PHP  has  all  the  tools

66

And  there  are  more    roads  to  Rome

67

You’re  one-­‐stop  fix

68

phpunit.de

Need  help?

70

Michelangelo van Dam !michelangelo@in2it.be @DragonBe

www.in2it.be

71

CONFERENCEANTWERP 2015PHP

BENELUXJanuary 23 & 24, 2015!

Antwerp Belgium!!

!

phpcon.eu

72

joind.in/12480!!

Slides are here, leave some feedback!!

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

Ques3ons?

73

http

s://w

ww.

flick

r.com

/pho

tos/

mdp

ettit

t/867

1901

426

top related