create, test, secure, repeat

161
Create, Test, Secure & Repeat Part of the in2it Quality Assurance Training in it 2 PROFESSIONAL PHP SERVICES

Upload: michelangelo-van-dam

Post on 02-Aug-2015

295 views

Category:

Engineering


5 download

TRANSCRIPT

Create, Test, Secure & Repeat

Part of the in2it Quality Assurance Training

in it2PROFESSIONAL PHP SERVICES

http

s://w

ww.

flick

r.com

/pho

tos/

9037

1939

@N

00/4

3448

7810

4

Michelangelo van DamPHP Consultant, Community Leader & Trainer

http

s://w

ww.

flick

r.com

/pho

tos/

akra

bat/8

7843

1881

3

http

s://w

ww.

flick

r.com

/pho

tos/

erik

torn

er/8

0484

2872

9

Introduction

Mise en place (preparation)

Running Tests

Starting a new project with TDD

Testing & Improving legacy code

Other tools

Recap & Closing remarks

http

s://w

ww.

flick

r.com

/pho

tos/

ryan

tyle

rsm

ith/1

4010

1048

72

Introduction

Mise en place (preparation)

Running Tests

Starting a new project with TDD

Testing & Improving legacy code

Other tools

Recap & Closing remarks

http

s://w

ww.

flick

r.com

/pho

tos/

ryan

tyle

rsm

ith/1

4010

1048

72

PHPUnit• Created by Sebastian

Bergmann in 2004

• Port of xUnit to PHP

• Uses assertions for testing

• Supports

• Unit testing

• Integration testing w/ DBUnit

• Acceptance testing w/ Selenium

http

s://w

ww.

flick

r.com

/pho

tos/

gree

nboy

/389

5219

425

http

s://w

ww.

flick

r.com

/pho

tos/

gree

nboy

/389

5219

425

http

s://w

ww.

flick

r.com

/pho

tos/

toni

vc/

http

s://w

ww.

flick

r.com

/pho

tos/

gree

nboy

/389

5219

425

http

s://w

ww.

flick

r.com

/pho

tos/

toni

vc/

http

s://w

ww.

flick

r.com

/pho

tos/

6875

1915

@N

05/6

7361

5804

5

http

s://w

ww.

flick

r.com

/pho

tos/

akra

bat/8

4215

6017

8

http

s://w

ww.

flick

r.com

/pho

tos/

jake

rust

/162

2366

9794

A little test

Who created PHPUnit and is now project lead?

A. Chris Hartjes

B. Sebastian Bergmann

C. Stefan Priepsch

With PHPUnit you can…?

A. Test the smallest functional piece of code (unit)

B. Test integrations with a database (integration)

C. Test automated acceptance testing with Selenium

D. All of the above

E. None of the above

An assertion is…?

A. Verifying that an expected value matches the result of a process?

B. Verifying that a process produces results

C. A transformation of a value

Assertion

• is a true/false statement

• to match an expectation

• with the result of functionality under test

Available assertions• assertArrayHasKey() • assertArraySubset() • 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() • assertStringEndsWith() • assertStringEqualsFile() • assertStringStartsWith() • assertThat() • assertTrue() • assertXmlFileEqualsXmlFile() • assertXmlStringEqualsXmlFile() • assertXmlStringEqualsXmlString()

Available assertions• assertArrayHasKey() • assertArraySubset() • 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()!• assertStringEndsWith() • assertStringEqualsFile() • assertStringStartsWith() • assertThat() • assertTrue()!• assertXmlFileEqualsXmlFile() • assertXmlStringEqualsXmlFile() • assertXmlStringEqualsXmlString()

Example usage Assertions

// Asserting a value returned by $myClass->myMethod() is TRUE "$this->assertTrue($myClass->myMethod()); "!// Asserting that a string matches type and value of $myClass->toString() "$this->assertSame('my string', $myClass->toString()); "!// Asserting that the value matches $myClass->addOne(0), type check not necessary "$this->assertEquals('1', $myClass->addOne(0)); "!// Assserting that the result of $myClass->getBirthday()->format('Y')  "// is greater than the expected value "$this->assertGreaterThan(1900, $myClass->getBirthday()->format('Y')); "!// Asserting a value is NULL with a specified error message "$this->assertNull( "    $myClass->getProperty(),  "    'When instantiating MyClass the property value should be NULL but is ' . $myClass->getProperty() ");

Annotations

• Provide automatic features

• to execute arbitrary functionality

• without having to create logic

Available annotations• @author • @after • @afterClass • @backupGlobals • @backupStaticAttributes • @before • @beforeClass • @codeCoverageIgnore* • @covers • @coversDefaultClass • @coversNothing • @dataProvider • @depends • @expectedException • @expectedExceptionCode

• @expectedExceptionMessage • @expectedExceptionMessageRegExp • @group • @large • @medium • @preserveGlobalState • @requires • @runTestsInSeparateProcesses • @runInSeparateProcess • @small • @test • @testdox • @ticket • @uses

@group<?php "!class OrderTest extends \PHPUnit_Framework_TestCase "{ "    /** "     * @group Order "     */ "    public function testCanCreateOrder() "    { "        // ... test logic goes here "    } "     "    /** "     * @group Order "     * @group BUG-1234 "     */ "    public function testOrdersCanNotContainSoldProducts() "    { "        // ... test logic goes here "    } "}

How to use @group

# Run phpunit only against tests for the Order module./vendor/bin/phpunit --group Order!# Run phpunit for all tests except for the Order module./vendor/bin/phpunit --exclude-group Order

@dataProvider<?php "!class OrderTest extends \PHPUnit_Framework_TestCase "{ "    public function badDataProvider() "    { "        return [ "            [0, 0, 0], // we don't accept ID's less or equal than 0 "            ['', new \stdClass(), []], // only integer and float values "            [null, null, null], // no NULL values allowed "        ]; "    } "     "    /** "     * @dataProvider badDataProvider "     */ "    public function testAddProductToOrder($orderId, $productId, $price) "    { "        $order = new Order(); "        $result = $order->addProductToOrder($orderId, $productId, $price); "        $this->assertFalse($result); "    } "}

@expectedException<?php "!class OrderTest extends \PHPUnit_Framework_TestCase "{ "    /** "     * @expectedException \InvalidArgumentException "     * @expectedExceptionMessage The order ID cannot be null "     */ "    public function testAddProductToOrder() "    { "        $orderId = null; "        $productId = null; "        $price = null; "        $order = new Order(); "        $result = $order->addProductToOrder($orderId, $productId, $price); "        $this->fail('Expected exception was not thrown'); "    } "}

www.phpunit.de

Introduction

Mise en place (preparation)

Running Tests

Starting a new project with TDD

Testing & Improving legacy code

Other tools

Recap & Closing remarks

http

s://w

ww.

flick

r.com

/pho

tos/

ryan

tyle

rsm

ith/1

4010

1048

72

http

s://w

ww.

flick

r.com

/pho

tos/

427/

2253

5300

41

Getting PHPUnit

Composer way (preferred) Download Composer curl -sS https://getcomposer.org/installer | php!Get phpunit php composer.phar require phpunit/phpunit!Direct download curl -O https://phar.phpunit.de/phpunit.phar

http

s://w

ww.

flick

r.com

/pho

tos/

ruef

ul/5

9065

4659

9

For this trainingInstallation of source code !Clone the repository into your workspace before attending the workshop. git clone https://github.com/in2it/ctsr-workshop.git cd ctsr-workshop/!Once you have cloned the training package, make sure you install composer. curl -sS https://getcomposer.org/installer | php!When the download is done, install required components using composer php composer.phar install

For this trainingInstallation of source code !Clone the repository into your workspace before attending the workshop. git clone https://github.com/in2it/ctsr-workshop.git cd ctsr-workshop/!Once you have cloned the training package, make sure you install composer. curl -sS https://getcomposer.org/installer | php!When the download is done, install required components using composer php composer.phar install ht

tps:

//ww

w.fli

ckr.c

om/p

hoto

s/in

telfr

eepr

ess/

1398

3474

320

During the workshop you're asked to solve several exercises. All example codes are based on a UNIX-like OS, so if you plan to participate this workshop with another OS, you need to know what changes are required to have the exercises run on your operating system.The exercises, the source code and the examples are tested on the following platforms: • Windows 7 • Mac OS X • Ubuntu Linux !When you need to switch to a specific exercise branch (e.g. ex-0.0), you can do this with the following command. git checkout -b ex-0.0 origin/ex-0.0

http

s://w

ww.

flick

r.com

/pho

tos/

rhin

onea

l/806

0238

470

phpunit.xml

<?xml  version="1.0"  encoding="utf-­‐8"?>  <phpunit          bootstrap="./vendor/autoload.php"          colors="true"          stopOnFailure="true"          stopOnError="true"          syntaxCheck="true">  !        <testsuite  name="Unit  Tests">                  <directory>./tests</directory>          </testsuite>  !        <filter>                  <whitelist>                          <directory  suffix=".php">./src</directory>                  </whitelist>          </filter>  !        <logging>                  <log                          type="coverage-­‐html"                          target="./build/coverage"                          charset="UTF-­‐8"                          yui="true"                          highlight="true"                          lowUpperBound="35"                          highLowerBound="70"/>                  <log  type="coverage-­‐xml"  target="./build/logs/coverage.xml"/>                  <log  type="coverage-­‐clover"  target="./build/logs/clover.xml"/>                  <log  type="tap"  target="./build/logs/phpunit.tap"/>                  <log  type="testdox-­‐text"  target="./build/logs/testdox.txt"/>                  <log  type="junit"  target="./build/logs/junit.xml"  logIncompleteSkipped="false"/>          </logging>  !</phpunit>

Introduction

Mise en place (preparation)

Running Tests

Starting a new project with TDD

Testing & Improving legacy code

Other tools

Recap & Closing remarks

http

s://w

ww.

flick

r.com

/pho

tos/

ryan

tyle

rsm

ith/1

4010

1048

72

http

s://w

ww.

flick

r.com

/pho

tos/

cgc/

6776

701

Composer PHPUnit ./vendor/bin/phpunit!

Phar PHPUnit php phpunit.phar

Exercise 0.0

• What will happen when you run PHPUnit now?

• Checkout branch ex-0.0 git checkout -b ex-0.0 origin/ex-0.0

• Run PHPUnit

Let’s write our first test<?php "namespace In2it\Test\Workshop\Ctsr; "!use In2it\Workshop\Ctsr\SampleClass; "!class SampleClassTest extends \PHPUnit_Framework_TestCase "{ "    public function testSomethingReturnsGreeting() "    { "        $sampleClass = new SampleClass(); "        $this->assertSame( "            'Hello World!', $sampleClass->doSomething() "        ); "    } "}

Exercise 0.1

• What will happen when you run PHPUnit now?

• Checkout branch ex-0.1 git checkout -b ex-0.1 origin/ex-0.1

• Run PHPUnit

Write our class

<?php "namespace In2it\Workshop\Ctsr; "!class SampleClass "{ "    public function doSomething() "    { "        return 'Hello World!'; "    } "}

Exercise 0.2

• What will happen when you run PHPUnit now?

• Checkout branch ex-0.2 git checkout -b ex-0.2 origin/ex-0.2

• Run PHPUnit

Exercise 0.3

• Test that you can provide an argument and the argument will be returned as “Hello <arg>!”

• Write the test

• Modify the class

Modifying the test class

    public function testSomethingReturnsArgument() "    { "        $sampleClass = new SampleClass(); "        $argument = 'Class'; "        $this->assertSame( "            sprintf('Hello %s!', $argument), "            $sampleClass->doSomething($argument) "        ); "    }

Modify the SampleClass

<?php "namespace In2it\Workshop\Ctsr; "!class SampleClass "{ "    public function doSomething($argument) "    { "        return 'Hello ' . $argument . '!'; "    } "}

Update further

<?php "namespace In2it\Workshop\Ctsr; "!class SampleClass "{ "    public function doSomething($argument = 'World') "    { "        return 'Hello ' . $argument . '!'; "    } "}

Chapter 0 What have you learned

• How to install phpunit

• How to configure phpunit

• How to write your test first

• How to modify requirements through testing

• How to debug failures and fix them easily with tests

http

s://w

ww.

flick

r.com

/pho

tos/

rast

er/3

5631

3580

4

Introduction

Mise en place (preparation)

Running Tests

Starting a new project with TDD

Testing & Improving legacy code

Other tools

Recap & Closing remarks

http

s://w

ww.

flick

r.com

/pho

tos/

ryan

tyle

rsm

ith/1

4010

1048

72

http

s://w

ww.

flick

r.com

/pho

tos/

esot

erik

a/53

4799

8757

Schedule Manager

Functional requirements

• Application needs to manage cron entries, where all scheduled tasks are stored in a database and written to the crontab when saved.

What is crontab?• A tool on UNIX-like systems to execute tasks at a certain interval

• Each line contains the following items:

• Minutes and/or interval

• Hours and/or interval

• Days of the month (DOM) and/or interval

• Months and/or interval

• Days of the week (DOW) and/or interval

• Executable command

Example crontab # Minutes Hours DOM Months DOW Command!# Warm up caches with new products# Every 5 minutes each day*/5 * * * * /bin/sh /path/to/productCollector.sh 2>&1!# Send marketing mail to active customers# Every Monday at 9:30am30 9 * * 1 /usr/bin/php /path/to/marketingMailSender.php 2>&1!# Clean up waste# Every day at 6am, 1pm and 5pm from Monday to Friday0 6,13,17 * * 1-5 /bin/sh /path/to/wasteCleaner.sh 2>&1

Analysis• crontab = collection of entries • Each entry contains 5 assets and a command • Each asset can contain a

• Wildcard (full range) • Range n-m (subset of full range) • List n,m,o • A single value n

• With similar intervals (without the wildcard)

http

s://w

ww.

flick

r.com

/pho

tos/

cave

man

_922

23/3

9683

5438

7

• cronmanager • collection of crontab entries • each entry contains

• minutes (collection) and interval (collection) • hours (collection) and interval (collection) • days of the month (collection) and interval (collection) • months (collection) and interval (collection) • days of the week (collection) and interval (collection) • command string

• each entry collection (minutes, hours, dom, months, dow) requires a range • minutes (0 - 59) • hours (0 - 23) • days of the month (1 - 31) • month (1 - 12) • days of the week (0 - 7) (0 & 7 are Sunday)

• crontab is write-only, so we need to update the full crontab completely

<?php "namespace In2it\Test\Workshop\Ctsr; "!class CronManagerTest extends \PHPUnit_Framework_TestCase "{ "!    public function testCronManagerCanAddAnEntry() "    { "        $entry = new \stdClass(); "        $cronman = new CronManager(); "        $cronman->addEntry($entry); "!        $this->assertCount(1, $cronman); "    } "}

<?php "namespace In2it\Workshop\Ctsr; "!class CronManager implements \Countable, \Iterator "{ "    protected $stack; "    protected $pointer; "    protected $counter; "!    public function addEntry($entry) "    { "        $this->stack[] = $entry; "        $this->counter++; "    } "!    // Implement the Iterator and Countable methods here. "    // - Iterator: http://php.net/manual/en/class.iterator.php "    // - Countable: http://php.net/manual/en/class.countable.php  "    public function current() {/**... */} "    public function next() {/**... */} "    public function key() {/**... */} "    public function valid() {/**... */} "    public function rewind() {/**... */} "    public function count() {/**... */} "}

!<?php "namespace In2it\Test\Workshop\Ctsr; "!use In2it\Workshop\Ctsr\CronManager; "!class CronManagerTest extends \PHPUnit_Framework_TestCase "{ "!    public function testCronManagerCanAddAnEntry() "    { "        $entry = new \stdClass(); "        $cronman = new CronManager(); "        $cronman->addEntry($entry); "!        $this->assertCount(1, $cronman); "    } "}

!<?php "namespace In2it\Test\Workshop\Ctsr; "!use In2it\Workshop\Ctsr\CronManager; "!class CronManagerTest extends \PHPUnit_Framework_TestCase "{ "!    public function testCronManagerCanAddAnEntry() "    { "        $entry = new \stdClass(); "        $cronman = new CronManager(); "        $cronman->addEntry($entry); "!        $this->assertCount(1, $cronman); "    } "}

Exercise 1.0

• Checkout branch ex-1.0

• Create a test for the Entry class

Something like this…

<?php "namespace In2it\Test\Workshop\Ctsr; "!use In2it\Workshop\Ctsr\CronManager\Entry; "!class EntryTest extends \PHPUnit_Framework_TestCase "{ "    public function testEntryContainsAllFields() { /** ... */ } "!    public function testEntryCanSetEntryElements() { /** ... */ } "}

Something like this… (2)

public function testEntryContainsAllFields() "{ "    $entry = new Entry(); "!    $this->assertCount(0, $entry->getMinutes()); "    $this->assertCount(0, $entry->getHours()); "    $this->assertCount(0, $entry->getDom()); "    $this->assertCount(0, $entry->getMonths()); "    $this->assertCount(0, $entry->getDow()); "    $this->assertSame('', $entry->getCommand()); "}

Something like this… (3)public function testEntryCanSetEntryElements() "{ "    $assetCollection = $this->getMock( "        'In2it\\Workshop\\Ctsr\\CronManager\\AssetCollection' "    ); "    $entry = new Entry(); "!    $entry->setMinutes($assetCollection); "    $this->assertInstanceOf( "        'In2it\\Workshop\\Ctsr\\CronManager\\AssetCollection',  "        $entry->getMinutes() "    ); "!    /*  "     * Similar routines for Hours, Days of the Month, Months and Days of the week "     */ "!    $command = $this->getMock('In2it\\Workshop\\Ctsr\\CronManager\\Command'); "    $entry->setCommand($command); "    $this->assertInstanceOf( "        'In2it\\Workshop\\Ctsr\\CronManager\\Command',  "        $entry->getCommand() "    ); "}

Overview of classes (ex-1.0) ctsr-workshop/ src/ ex-1.0/ CronManager.php tests/ ex-1.0/ CronManager/ EntryTest.php CronManagerTest.php

Exercise 1.1

• Checkout branch ex-1.1

• Have a look at all the tests

Pop-quiz

// Why are we using Mock objects to test functionality? "$assetCollection = $this->getMock( "    'In2it\\Workshop\\Ctsr\\CronManager\\AssetCollection' "); "!$asset = $this->getMock( "    'In2it\\Workshop\\Ctsr\\CronManager\\Asset' "); "!$entry = $this->getMock( "    'In2it\\Workshop\\Ctsr\\CronManager\\Entry' ");

Question

• Are we protected against bad input?

• Yes

• No

Create some bad data

public function badDataProvider() "{ "    return array ( "        array ('foo'), "        array (new \stdClass()), "        array (array ()), "        array (1.50), "    ); "}

And let’s test it!

/** " * @dataProvider badDataProvider " * @covers In2it\Workshop\Ctsr\CronManager\Asset::__construct " * @covers In2it\Workshop\Ctsr\CronManager\Asset::setValue " * @covers In2it\Workshop\Ctsr\CronManager\Asset::getValue " * @expectedException \InvalidArgumentException " */ "public function testRejectBadData($badData) "{ "    $asset = new Asset($badData); "    $this->fail('Expected InvalidArgumentException to be thrown'); "}

Let’s fix that!/** " * @param int $value " * @throws \InvalidArgumentException " */ "public function setValue($value) "{ "    if (!is_int($value)) { "        throw new \InvalidArgumentException( "            'You\'ve provided an invalid argument' "        ); "    } "    if (false === ($result = filter_var($value, FILTER_VALIDATE_INT))) { "        throw new \InvalidArgumentException( "            'You\'ve provided an invalid argument' "        ); "    } "    $this->value = (int) $value; "}

Complete code in branch ex-1.2

Introduction

Mise en place (preparation)

Running Tests

Starting a new project with TDD

Testing & Improving legacy code

Other tools

Recap & Closing remarks

http

s://w

ww.

flick

r.com

/pho

tos/

ryan

tyle

rsm

ith/1

4010

1048

72

http

s://w

ww.

flick

r.com

/pho

tos/

cmdr

cord

/964

5186

380

Legacy code

• Code that was already written

• Not (always) adhering to best practices

• Not (always) testable

• What developers hate working on

http

s://w

ww.

flick

r.com

/pho

tos/

arch

er10

/784

5300

746

<?php "!class ModuleManager "{ "    public static $modules_install = array(); "    /** "     * Includes file with module installation class. "     * "     * Do not use directly. "     * "     * @param string $module_class_name module class name - underscore separated "     * @return bool "     */ "    public static final function include_install($module_class_name) { "        if(isset(self::$modules_install[$module_class_name])) return true; "        $full_path = __DIR__ . '/modules/' . $module_class_name . '/' . $module_class_name . '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 ' . $module_class_name . ': Invalid install file', E_USER_ERROR); "        self::$modules_install[$module_class_name] = new $x($module_class_name); "        return true; "    } "!}

<?php "!include_once __DIR__ . '/../../src/ex-2.0/ModuleManager.php'; "!class ModuleManagerTest extends \PHPUnit_Framework_TestCase "{ "    /** "     * @covers ModuleManager::include_install "     */ "    public function testModuleManagerCanLoadMailModule() "    { "        $result = ModuleManager::include_install('Mail'); "        $this->assertTrue($result); "    } "}

<?php "!class ModuleManager "{ "    public static $modules_install = array(); "    /** "     * Includes file with module installation class. "     * "     * Do not use directly. "     * "     * @param string $module_class_name module class name - underscore separated "     * @return bool "     */ "    public static final function include_install($module_class_name) { "        if(isset(self::$modules_install[$module_class_name])) return true; "        $full_path = __DIR__ . '/modules/' . $module_class_name . '/' . $module_class_name . '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 ' . $module_class_name . ': Invalid install file', E_USER_ERROR); "        self::$modules_install[$module_class_name] = new $x($module_class_name); "        return true; "    } "!}

<?php "!class ModuleManager "{ "    public static $modules_install = array(); "    /** "     * Includes file with module installation class. "     * "     * Do not use directly. "     * "     * @param string $module_class_name module class name - underscore separated "     * @return bool "     */ "    public static final function include_install($module_class_name) { "        if(isset(self::$modules_install[$module_class_name])) return true; "        $full_path = __DIR__ . '/modules/' . $module_class_name . '/' . $module_class_name . '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 ' . $module_class_name . ': Invalid install file', E_USER_ERROR); "        self::$modules_install[$module_class_name] = new $x($module_class_name); "        return true; "    } "!}

<?php "!class ModuleManager "{ "    public static $modules_install = array(); "    /** "     * Includes file with module installation class. "     * "     * Do not use directly. "     * "     * @param string $module_class_name module class name - underscore separated "     * @return bool "     */ "    public static final function include_install($module_class_name) { "        if(isset(self::$modules_install[$module_class_name])) return true; "        $full_path = __DIR__ . '/modules/' . $module_class_name . '/' . $module_class_name . '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 ' . $module_class_name . ': Invalid install file', E_USER_ERROR); "        self::$modules_install[$module_class_name] = new $x($module_class_name); "        return true; "    } "!}

<?php "!class ModuleManager "{ "    public static $modules_install = array(); "    /** "     * Includes file with module installation class. "     * "     * Do not use directly. "     * "     * @param string $module_class_name module class name - underscore separated "     * @return bool "     */ "    public static final function include_install($module_class_name) { "        if(isset(self::$modules_install[$module_class_name])) return true; "        $full_path = __DIR__ . '/modules/' . $module_class_name . '/' . $module_class_name . '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 ' . $module_class_name . ': Invalid install file', E_USER_ERROR); "        self::$modules_install[$module_class_name] = new $x($module_class_name); "        return true; "    } "!}

<?php "!class ModuleManager "{ "    public static $modules_install = array(); "    /** "     * Includes file with module installation class. "     * "     * Do not use directly. "     * "     * @param string $module_class_name module class name - underscore separated "     * @return bool "     */ "    public static final function include_install($module_class_name) { "        if(isset(self::$modules_install[$module_class_name])) return true; "        $full_path = __DIR__ . '/modules/' . $module_class_name . '/' . $module_class_name . '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 ' . $module_class_name . ': Invalid install file', E_USER_ERROR); "        self::$modules_install[$module_class_name] = new $x($module_class_name); "        return true; "    } "!}

<?php "!include_once __DIR__ . '/../../src/ex-2.0/ModuleManager.php'; "!class ModuleManagerTest extends \PHPUnit_Framework_TestCase "{ "    /** "     * @covers ModuleManager::include_install "     */ "//    public function testModuleManagerCanLoadMailModule() "//    { "//        $result = ModuleManager::include_install('Mail'); "//        $this->assertTrue($result); "//    } "}

http

s://w

ww.

flick

r.com

/pho

tos/

mar

cgbx

/780

3086

292

/** " * @covers ModuleManager::include_install " */ "public function testReturnImmediatelyWhenModuleAlreadyLoaded() "{ "    $module = 'Foo_Bar'; "    ModuleManager::$modules_install[$module] = 1; "    $result = ModuleManager::include_install($module); "    $this->assertTrue($result); "    $this->assertCount(1, ModuleManager::$modules_install); "}

http

s://w

ww.

flick

r.com

/pho

tos/

chris

tian_

joha

nnes

en/2

2482

4478

6

/** " * @covers ModuleManager::include_install " */ "public function testReturnWhenModuleIsNotFound() "{ "    $module = 'Foo_Bar'; "    $result = ModuleManager::include_install($module); "    $this->assertFalse($result); "    $this->assertEmpty(ModuleManager::$modules_install); "}

public static final function include_install($module_class_name) { "    if(isset(self::$modules_install[$module_class_name])) return true; "    $full_path = __DIR__ . '/modules/' . $module_class_name . '/' . $module_class_name . '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 ' . $module_class_name . ': Invalid install file', E_USER_ERROR); "    self::$modules_install[$module_class_name] = new $x($module_class_name); "    return true; "}

self::$modules_install[$module_class_name]

protected function tearDown() "{ "    ModuleManager::$modules_install = array (); "}

http

s://w

ww.

flick

r.com

/pho

tos/

evae

kebl

ad/1

4780

0905

50

/** " * @covers ModuleManager::include_install " * @expectedException \PHPUnit_Framework_Error " */ "public function testTriggerErrorWhenInstallClassDoesNotExists() "{ "    $module = 'EssClient'; "    $result = ModuleManager::include_install($module); "    $this->fail('Expecting loading module EssClient would trigger and error'); "}

public static final function include_install($module_class_name) { "    if(isset(self::$modules_install[$module_class_name])) return true; "    $full_path = __DIR__ . '/modules/' . $module_class_name . '/' . $module_class_name . '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 ' . $module_class_name . ': Invalid install file', E_USER_ERROR); "    self::$modules_install[$module_class_name] = new $x($module_class_name); "    return true; "}

public static final function include_install($module_class_name) { "    if(isset(self::$modules_install[$module_class_name])) return true; "    $full_path = __DIR__ . '/modules/' . $module_class_name . '/' . $module_class_name . '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 ' . $module_class_name . ': Invalid install file', E_USER_ERROR); "    self::$modules_install[$module_class_name] = new $x($module_class_name); "    return true; "}

if (!file_exists($full_path)) return false;

Current Filestructure

|-- ModuleManager.php "`-- modules "    |-- EssClient "    |   `-- EssClient.php "    |-- IClient "    |   `-- IClientInstall.php "    `-- Mail "        `-- MailInstall.php

http

s://w

ww.

flick

r.com

/pho

tos/

sis/

2497

9123

43

http

s://w

ww.

flick

r.com

/pho

tos/

fragi

lete

nder

/533

2586

299

Current Filestructure

|-- ModuleManager.php "`-- modules "    |-- EssClient "    |   `-- EssClient.php "    |-- IClient "    |   `-- IClientInstall.php "    `-- Mail "        `-- MailInstall.php

/** " * @covers ModuleManager::include_install " * @expectedException \PHPUnit_Framework_Error " */ "public function testTriggerErrorWhenInstallClassDoesNotExists() "{ "    $module = 'IClient'; "    $result = ModuleManager::include_install($module); "    $this->fail('Expecting loading module EssClient would trigger and error'); "}

/** " * @covers ModuleManager::include_install " */ "public function testModuleManagerCanLoadMailModule() "{ "    $result = ModuleManager::include_install('Mail'); "    $this->assertTrue($result); "}

Get the codebranch ex-2.0

http

s://w

ww.

flick

r.com

/pho

tos/

ahhy

eah/

4544

9439

6

What to do

• Your legacy code has no return values?

    /** "     * Process Bank Payment files "     */ "    public function processBankPayments() "    { "        $this->getLogger()->log('Starting bank payment process', Zend_Log::INFO); "        foreach ($this->_getBankFiles() as $bankFile) { "            $bankData = $this->_processBankFile($bankFile); "            $this->getLogger()->log('Processing ' . $bankData->transactionId, "                Zend_Log::DEBUG "            ); "            /** @var Contact_Model_Contact $contact */ "            $contact = $this->getMapper('Contact_Model_Mapper_Contact') "                ->findContactByBankAccount($bankData->transactionAccount); "            if (null !== $contact) { "                $this->getLogger()->log(sprintf( "                    'Found contact "%s" for bank account %s', "                    $contact->getName(), "                    $bankData->transactionAccount "                ), Zend_Log::DEBUG); "                $data = array ( "                    'amount' => $bankData->transactionAmount, "                    'payment_date' => $bankData->transactionDate "                ); "                $this->getMapper('Invoice_Model_Mapper_Payments') "                    ->updatePayment($data, "                        array ('contact_id = ?' => $contact->getContactId()) "                    ); "                $this->_moveBankFile($bankFile, "                    $this->getPath() . DIRECTORY_SEPARATOR . self::PROCESS_SUCCEEDED "                ); "            } else { "                $this->getLogger()->log(sprintf( "                    'Could not match bankaccount "%s" with a contact', "                    $bankData->transactionAccount "                ), Zend_Log::WARN); "                $this->_moveBankFile($bankFile, "                    $this->getPath() . DIRECTORY_SEPARATOR . self::PROCESS_FAILED "                ); "            } "        } "    }

    public function testProcessingBankPayments() "    { "        $contact = $this->getMock( "            'Contact_Model_Contact', "            array ('getContactId', 'getName') "        ); "        $contact->expects($this->any()) "            ->method('getContactId') "            ->will($this->returnValue(1)); "        $contact->expects($this->any()) "            ->method('getName') "            ->will($this->returnValue('Foo Bar')); "        $contactMapper = $this->getMock('Contact_Model_Mapper_Contact', "            array ('findContactByBankAccount') "        ); "        $contactMapper->expects($this->any()) "            ->method('findContactByBankAccount') "            ->will($this->returnValue($contact)); "        $paymentsMapper = $this->getMock('Invoice_Model_Mapper_Payments', "            array ('updatePayment') "        ); "!        $logMock = new Zend_Log_Writer_Mock(); "        $logger = new Zend_Log(); "        $logger->setWriter($logMock); "        $logger->setPriority(Zend_Log::DEBUG); "!        $as400 = new Payments_Service_As400(); "        $as400->addMapper($contactMapper, 'Contact_Model_Mapper_Contact') "            ->addMapper($paymentsMapper, 'Invoice_Model_Mapper_Payments') "            ->setPath(__DIR__ . DIRECTORY_SEPARATOR . '_files') "            ->setLogger($logger); "!        $as400->processBankPayments(); "        $this->assertCount(3, $logMock->events); "        $this->assertEquals('Processing 401341345', $logMock->events[1]); "        $this->assertEquals( "            'Found contact "Foo Bar" for bank account BE93522511513933', "            $logMock->events[2] "        ); "    }

        $as400 = new Payments_Service_As400(); "        $as400->addMapper($contactMapper, 'Contact_Model_Mapper_Contact') "            ->addMapper($paymentsMapper, 'Invoice_Model_Mapper_Payments') "            ->setPath(__DIR__ . DIRECTORY_SEPARATOR . '_files') "            ->setLogger($logger); "!        $as400->processBankPayments(); "        $this->assertCount(3, $logMock->events); "        $this->assertEquals('Processing 401341345', $logMock->events[1]); "        $this->assertEquals( "            'Found contact "Foo Bar" for bank account BE93522511513933', "            $logMock->events[2] "        );

Get the codebranch ex-2.1

http

s://w

ww.

flick

r.com

/pho

tos/

ahhy

eah/

4544

9439

6

Introduction

Mise en place (preparation)

Running Tests

Starting a new project with TDD

Testing & Improving legacy code

Other tools

Recap & Closing remarks

http

s://w

ww.

flick

r.com

/pho

tos/

ryan

tyle

rsm

ith/1

4010

1048

72

http

s://w

ww.

flick

r.com

/pho

tos/

floria

nric

/726

3382

550

SCM is a must!

FTP is not a SCM

Use Composer

Automation tools

https://www.gnu.org/graphics/heckert_gnu.small.png

Common CI systems

Online CI systems

Online CI systems

Online CI systems

Some other test tools

https://www.gnu.org/graphics/heckert_gnu.small.png

Introduction

Mise en place (preparation)

Running Tests

Starting a new project with TDD

Testing & Improving legacy code

Other tools

Recap & Closing remarks

http

s://w

ww.

flick

r.com

/pho

tos/

ryan

tyle

rsm

ith/1

4010

1048

72

http

s://w

ww.

flick

r.com

/pho

tos/

didm

ysel

f/803

0013

349

http

s://w

ww.

flick

r.com

/pho

tos/

wjs

erso

n/33

1085

1114

http

s://w

ww.

flick

r.com

/pho

tos/

joes

hlab

otni

k/23

8449

5536

http

s://w

ww.

flick

r.com

/pho

tos/

muc

h0/8

5523

5390

1

http

s://w

ww.

flick

r.com

/pho

tos/

thom

asha

wk/

1049

0113

913

in it2PROFESSIONAL PHP SERVICES

Michelangelo van Dam!Zend Certified Engineer

[email protected] - www.in2it.be - T in2itvof - F in2itvof

PHPUnit!!

Getting Started!Advanced Testing

Zend Framework 2!!

Fundamentals!Advanced

Azure PHP!!

Quick time to market!Scale up and out

jQuery!!

Professional jQuery

PHP!!

PHP for beginners!Professional PHP

HTML & CSS!!

The Basics

Our training courses

http

s://w

ww.

flick

r.com

/pho

tos/

drew

m/3

1918

7251

5