Your code are my tests

74
Your code are my tests How to test legacy code in it 2 PROFESSIONAL PHP SERVICES

Transcript of Your code are my tests

Page 1: Your code are my tests

Your code are my tests

How to test legacy code

in it2PROFESSIONAL PHP SERVICES

Page 2: Your code are my tests

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

Michelangelo van Dam

PHP Consultant Community Leader

President of PHPBenelux Contributor to PHP projects

T @DragonBe | F DragonBe

http

s://w

ww.

flick

r.com

/pho

tos/

akra

bat/8

7843

1881

3

Page 4: Your code are my tests

Using Social Media?Tag it #mytests

http

://w

ww.

flick

r.com

/pho

tos/

andy

ofne

/463

3356

197

http

://w

ww.

flick

r.com

/pho

tos/

andy

ofne

/463

3356

197

Page 5: Your code are my tests

Why bother with testing?

http

s://w

ww.

flick

r.com

/pho

tos/

vial

bost

/553

3266

530

Page 6: Your code are my tests

Most common excuses why developers don’t test

• no time

• no budget

• deliver tests after finish project (never)

• devs don’t know how

http

s://w

ww.

flick

r.com

/pho

tos/

dasp

rid/8

1479

8630

7

Page 7: Your code are my tests

No excuses!!!

Crea%ve  Co

mmon

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

/pho

tos/akrabat/8421560178

Page 8: Your code are my tests

Responsibility issue

• As a developer, it’s your job to

• write code & fixing bugs

• add documentation

• write & update unit tests

Page 9: Your code are my tests

Pizza principleTopping:  your  tests

Box:  your  documenta%on

Dough:  your  code

Page 10: Your code are my tests

Benefits of testing• Direct feedback (test fails)

• Once a test is made, it will always be tested

• Easy to refactor existing code (protection)

• Easy to debug: write a test to see if a bug is genuine

• Higher confidence and less uncertainty

Page 11: Your code are my tests

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: Martin Fowler

Page 12: Your code are my tests

Warming up

http

s://w

ww.

flick

r.com

/pho

tos/

bobj

agen

dorf/

8535

3168

36

Page 13: Your code are my tests

PHPUnit• PHPUnit is a port of xUnit testing framework

• Created by “Sebastian Bergmann”

• Uses “assertions” to verify behaviour of “unit of code”

• Open source and hosted on GitHub

• See https://github.com/sebastianbergmann/phpunit

• Can be installed using:

• PEAR

• PHAR

• Composer

Page 14: Your code are my tests

Approach for testing

• Instantiate a “unit-of-code”

• Assert expected result against actual result

• Provide a custom error message

Page 15: Your code are my tests

Available assertions• assertArrayHasKey() • assertClassHasAttribute() • assertClassHasStaticAttribute() • assertContains() • assertContainsOnly() • assertContainsOnlyInstancesOf() • assertCount()• assertEmpty()• assertEqualXMLStructure() • assertEquals()• assertFalse()• assertFileEquals() • assertFileExists() • assertGreaterThan() • assertGreaterThanOrEqual() • assertInstanceOf() • assertInternalType() • assertJsonFileEqualsJsonFile() • assertJsonStringEqualsJsonFile() • assertJsonStringEqualsJsonString()

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

Page 16: Your code are my tests

To  protect  and  to  serve

Page 17: Your code are my tests

Data is tainted, ALWAYS

HackersBAD DATA

Web ServicesStupid users

Page 20: Your code are my tests

OWASP top 10 exploits

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

Page 21: Your code are my tests

Filtering & Validation

Page 22: Your code are my tests

Smallest unit of code

http

s://w

ww.

flick

r.com

/pho

tos/

tool

stop

/454

6017

269

Page 23: Your code are my tests

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

Page 24: Your code are my tests

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

Page 25: Your code are my tests

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

Page 26: Your code are my tests

Example: testing 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());          }  }

Page 27: Your code are my tests

We don’t live in a fairy tale!

http

s://w

ww.

flick

r.com

/pho

tos/

bertk

not/8

1752

1490

9

Page 28: Your code are my tests

Real code, real apps

Page 29: Your code are my tests

github.com/Telaxus/EPESI

Page 30: Your code are my tests

Running the project

Page 31: Your code are my tests

Where are the TESTS?

Page 32: Your code are my tests

Where are the TESTS?

Page 33: Your code are my tests

Oh noes, no tests!

http

s://w

ww.

flick

r.com

/pho

tos/

mjh

agen

/297

3212

926

Page 34: Your code are my tests

Let’s get started

http

s://w

ww.

flick

r.com

/pho

tos/

npob

re/2

6015

8225

6

Page 35: Your code are my tests

How to get about it?

Page 36: Your code are my tests

Setting up for testing<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>

Page 37: Your code are my tests

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 • satisfy_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

Page 38: Your code are my tests

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

Page 39: Your code are my tests

Testing first condition<?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');     } }

Page 40: Your code are my tests

Run test

Page 41: Your code are my tests

Check coverage

Page 42: Your code are my tests

Test for second condition

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

Page 43: Your code are my tests

Run tests

Page 44: Your code are my tests

Check coverage

Page 45: Your code are my tests

Test for third condition

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

Page 46: Your code are my tests

Run tests

Page 47: Your code are my tests

Check code coverage

Page 48: Your code are my tests

Non-executable code

http

s://w

ww.

flick

r.com

/pho

tos/

dazj

ohns

on/7

7208

0682

4

Page 49: Your code are my tests

Test for success

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

Page 50: Your code are my tests

Run tests

Page 51: Your code are my tests

Check code coverage

Page 52: Your code are my tests

Look at the global coverage

Page 53: Your code are my tests

Bridging gaps

http

s://w

ww.

flick

r.com

/pho

tos/

hugo

90/6

9807

1264

3

Page 54: Your code are my tests

Privates exposed

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 55: Your code are my tests

Dependency• __construct

• get_module_name

• get_version_min

• get_version_max

• is_satisfied_by

• requires

• requires_exact

• requires_at_least

• requires_range

Page 56: Your code are my tests

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 ? '<=' : '<';     }

    /** ... */ }

Page 57: Your code are my tests

Don’t touch my junk!

http

s://w

ww.

flick

r.com

/pho

tos/

case

ymul

timed

ia/5

4122

9373

0

Page 58: Your code are my tests

House of Reflection

http

s://w

ww.

flick

r.com

/pho

tos/

tabo

r-roe

der/8

2507

7011

5

Page 59: Your code are my tests

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

Page 60: Your code are my tests

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

Page 61: Your code are my tests

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

Page 62: Your code are my tests

Run tests

Page 63: Your code are my tests

Code Coverage

Page 64: Your code are my tests

Yes, paradise exists

http

s://w

ww.

flick

r.com

/pho

tos/

rnug

raha

/200

3147

365

Page 65: Your code are my tests

Unit testing is not difficult!

Page 66: Your code are my tests

Get started

Page 67: Your code are my tests

PHP has all the tools

Page 68: Your code are my tests

And there are more roads to Rome

Page 69: Your code are my tests

You’re one-stop fix

phpunit.de

Page 71: Your code are my tests

http

s://w

ww.

flick

r.com

/pho

tos/

lwr/1

3442

5422

35

Page 72: Your code are my tests

Contact us

in it2PROFESSIONAL PHP SERVICES

Michelangelo van Dam [email protected]

www.in2it.be

PHP Consulting - Training - QA

Page 73: Your code are my tests

cfp.phpbenelux.eu

Submit your proposals before October 14, 2015

Page 74: Your code are my tests

Thank youHave a great conference

http

://w

ww.

flick

r.com

/pho

tos/

drew

m/3

1918

7251

5