SlideShare a Scribd company logo
Create, Test, Secure &
Repeat
Part of the in2it Quality Assurance Training
in it2PROFESSIONAL PHP SERVICES
https://www.flickr.com/photos/90371939@N00/4344878104
Michelangelo van Dam
PHP Consultant, Community Leader & Trainer
https://www.flickr.com/photos/akrabat/8784318813
Create, Test, Secure, Repeat
Workshop
Get prepared
https://github.com/in2it/ctsr-workshop
Requirements
5.4+
Requirements
- a computer with PHP 5.4 or higher
- the latest composer
- git
Exercises tested on
- Microsoft Windows 7 & 8
- Linux
- Mac OS X 10.10.2 or higher
Learn unit testing like a pro
https://www.flickr.com/photos/eriktorner/8048428729
Introduction
Mise en place (preparation)
Running Tests
Starting a new project with TDD
Testing & Improving legacy code
Other tools
Recap & Closing remarks
https://www.flickr.com/photos/ryantylersmith/14010104872
Introduction
Mise en place (preparation)
Running Tests
Starting a new project with TDD
Testing & Improving legacy code
Other tools
Recap & Closing remarks
https://www.flickr.com/photos/ryantylersmith/14010104872
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
https://www.flickr.com/photos/greenboy/3895219425
https://www.flickr.com/photos/greenboy/3895219425
https://www.flickr.com/photos/tonivc/
https://www.flickr.com/photos/greenboy/3895219425
https://www.flickr.com/photos/tonivc/
https://www.flickr.com/photos/68751915@N05/6736158045
https://www.flickr.com/photos/akrabat/8421560178
https://www.flickr.com/photos/jakerust/16223669794
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);
    }
}
Create, test, secure, repeat
@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');
    }
}
Create, test, secure, repeat
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
https://www.flickr.com/photos/ryantylersmith/14010104872
https://www.flickr.com/photos/427/2253530041
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
https://www.flickr.com/photos/rueful/5906546599
For this training
Installation 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 training
Installation 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
https://www.flickr.com/photos/intelfreepress/13983474320
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
https://www.flickr.com/photos/rhinoneal/8060238470
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
https://www.flickr.com/photos/ryantylersmith/14010104872
https://www.flickr.com/photos/cgc/6776701
Composer PHPUnit
./vendor/bin/phpunit
Phar PHPUnit
php phpunit.phar
Create, test, secure, repeat
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
php composer.phar dump-autoload
• Run PHPUnit
Create, test, secure, repeat
Let’s write our first test
<?php
namespace In2itTestWorkshopCtsr;
use In2itWorkshopCtsrSampleClass;
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
php composer.phar dump-autoload
• Run PHPUnit
Error?
Got Error?
error: Your local changes to the following files would be overwritten by checkout:
composer.lock
Please, commit your changes or stash them before you can switch branches.
Aborting
Solution
git checkout -- composer.lock
git checkout -b ex-0.1 origin/ex-0.1
Create, test, secure, repeat
Write our class
<?php
namespace In2itWorkshopCtsr;
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
php composer.phar dump-autoload
• Run PHPUnit
Create, test, secure, repeat
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)
        );
    }
Create, test, secure, repeat
Modify the SampleClass
<?php
namespace In2itWorkshopCtsr;
class SampleClass
{
    public function doSomething($argument)
    {
        return 'Hello ' . $argument . '!';
    }
}
Create, test, secure, repeat
Update further
<?php
namespace In2itWorkshopCtsr;
class SampleClass
{
    public function doSomething($argument = 'World')
    {
        return 'Hello ' . $argument . '!';
    }
}
Create, test, secure, repeat
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
https://www.flickr.com/photos/raster/3563135804
Introduction
Mise en place (preparation)
Running Tests
Starting a new project with TDD
Testing & Improving legacy code
Other tools
Recap & Closing remarks
https://www.flickr.com/photos/ryantylersmith/14010104872
https://www.flickr.com/photos/esoterika/5347998757
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:30am
30 9 * * 1 /usr/bin/php /path/to/marketingMailSender.php 2>&1
# Clean up waste
# Every day at 6am, 1pm and 5pm from Monday to Friday
0 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)
https://www.flickr.com/photos/caveman_92223/3968354387
• 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 In2itTestWorkshopCtsr;
class CronManagerTest extends PHPUnit_Framework_TestCase
{
    public function testCronManagerCanAddAnEntry()
    {
        $entry = new stdClass();
        $cronman = new CronManager();
        $cronman->addEntry($entry);
        $this->assertCount(1, $cronman);
    }
}
<?php
namespace In2itWorkshopCtsr;
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 In2itTestWorkshopCtsr;
use In2itWorkshopCtsrCronManager;
class CronManagerTest extends PHPUnit_Framework_TestCase
{
    public function testCronManagerCanAddAnEntry()
    {
        $entry = new stdClass();
        $cronman = new CronManager();
        $cronman->addEntry($entry);
        $this->assertCount(1, $cronman);
    }
}
<?php
namespace In2itTestWorkshopCtsr;
use In2itWorkshopCtsrCronManager;
class CronManagerTest extends PHPUnit_Framework_TestCase
{
    public function testCronManagerCanAddAnEntry()
    {
        $entry = new stdClass();
        $cronman = new CronManager();
        $cronman->addEntry($entry);
        $this->assertCount(1, $cronman);
    }
}
Create, test, secure, repeat
Exercise 1.0
• Checkout branch ex-1.0
• Create a test for the Entry class
Something like this…
<?php
namespace In2itTestWorkshopCtsr;
use In2itWorkshopCtsrCronManagerEntry;
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(
        'In2itWorkshopCtsrCronManagerAssetCollection'
    );
    $entry = new Entry();
    $entry->setMinutes($assetCollection);
    $this->assertInstanceOf(
        'In2itWorkshopCtsrCronManagerAssetCollection', 
        $entry->getMinutes()
    );
    /* 
     * Similar routines for Hours, Days of the Month, Months and Days of the week
     */
    $command = $this->getMock('In2itWorkshopCtsrCronManagerCommand');
    $entry->setCommand($command);
    $this->assertInstanceOf(
        'In2itWorkshopCtsrCronManagerCommand', 
        $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(
    'In2itWorkshopCtsrCronManagerAssetCollection'
);
$asset = $this->getMock(
    'In2itWorkshopCtsrCronManagerAsset'
);
$entry = $this->getMock(
    'In2itWorkshopCtsrCronManagerEntry'
);
Create, test, secure, repeat
Create, test, secure, repeat
Create, test, secure, repeat
Create, test, secure, repeat
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 In2itWorkshopCtsrCronManagerAsset::__construct
 * @covers In2itWorkshopCtsrCronManagerAsset::setValue
 * @covers In2itWorkshopCtsrCronManagerAsset::getValue
 * @expectedException InvalidArgumentException
 */
public function testRejectBadData($badData)
{
    $asset = new Asset($badData);
    $this->fail('Expected InvalidArgumentException to be thrown');
}
Create, test, secure, repeat
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;
}
Create, test, secure, repeat
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
https://www.flickr.com/photos/ryantylersmith/14010104872
https://www.flickr.com/photos/cmdrcord/9645186380
Legacy code
• Code that was already written
• Not (always) adhering to best
practices
• Not (always) testable
• What developers hate working
on
https://www.flickr.com/photos/archer10/7845300746
<?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);
    }
}
Create, test, secure, repeat
Create, test, secure, repeat
<?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);
//    }
}
https://www.flickr.com/photos/marcgbx/7803086292
/**
 * @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);
}
Create, test, secure, repeat
Create, test, secure, repeat
https://www.flickr.com/photos/christian_johannesen/2248244786
/**
 * @covers ModuleManager::include_install
 */
public function testReturnWhenModuleIsNotFound()
{
    $module = 'Foo_Bar';
    $result = ModuleManager::include_install($module);
    $this->assertFalse($result);
    $this->assertEmpty(ModuleManager::$modules_install);
}
Create, test, secure, repeat
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 ();
}
Create, test, secure, repeat
Create, test, secure, repeat
https://www.flickr.com/photos/evaekeblad/14780090550
/**
 * @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');
}
Create, test, secure, repeat
Create, test, secure, repeat
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
https://www.flickr.com/photos/sis/2497912343
https://www.flickr.com/photos/fragiletender/5332586299
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');
}
Create, test, secure, repeat
Create, test, secure, repeat
/**
 * @covers ModuleManager::include_install
 */
public function testModuleManagerCanLoadMailModule()
{
    $result = ModuleManager::include_install('Mail');
    $this->assertTrue($result);
}
Create, test, secure, repeat
Create, test, secure, repeat
Get the code
branch ex-2.0
https://www.flickr.com/photos/ahhyeah/454494396
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
                );
            }
        }
    }
Create, test, secure, repeat
    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]
        );
Create, test, secure, repeat
Create, test, secure, repeat
Get the code
branch ex-2.1
https://www.flickr.com/photos/ahhyeah/454494396
Privates exposed
http://www.slashgear.com/former-tsa-agent-admits-we-knew-full-body-scanners-didnt-work-31315288/
Dependency
• __construct
• get_module_name
• get_version_min
• get_version_max
• is_satisfied_by
• requires
• requires_exact
• requires_at_least
• requires_range
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 ? '<=' : '<';
    }
    /** ... */
}
Don’t touch my junk!
https://www.flickr.com/photos/caseymultimedia/5412293730
House of Reflection
https://www.flickr.com/photos/tabor-roeder/8250770115
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!
    }
}
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');
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');
Run tests
Code Coverage
Introduction
Mise en place (preparation)
Running Tests
Starting a new project with TDD
Testing & Improving legacy code
Other tools
Recap & Closing remarks
https://www.flickr.com/photos/ryantylersmith/14010104872
https://www.flickr.com/photos/florianric/7263382550
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
Create, test, secure, repeat
Create, test, secure, repeat
Create, test, secure, repeat
Create, test, secure, repeat
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
https://www.flickr.com/photos/ryantylersmith/14010104872
https://www.flickr.com/photos/didmyself/8030013349
https://www.flickr.com/photos/wjserson/3310851114
https://www.flickr.com/photos/joeshlabotnik/2384495536
https://www.flickr.com/photos/much0/8552353901
https://www.flickr.com/photos/thomashawk/10490113913
References
in it2PROFESSIONAL PHP SERVICES
Michelangelo van Dam
Zend Certified Engineer
training@in2it.be - 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
https://www.flickr.com/photos/drewm/3191872515

More Related Content

What's hot (20)

PDF
Selenium Clinic Eurostar 2012 WebDriver Tutorial
Alan Richardson
 
PDF
Nightwatch at Tilt
Dave King
 
PDF
Testing Web Applications
Seth McLaughlin
 
ZIP
Five Easy Ways to QA Your Drupal Site
Mediacurrent
 
PDF
Continuous Integration Testing in Django
Kevin Harvey
 
ZIP
Automated Frontend Testing
Neil Crosby
 
PDF
Unit testing - A&BP CC
JWORKS powered by Ordina
 
PDF
Front-End Testing: Demystified
Seth McLaughlin
 
ODP
WebTest - Efficient Functional Web Testing with HtmlUnit and Beyond
mguillem
 
PDF
Unit-testing and E2E testing in JS
Michael Haberman
 
PPTX
Unit testing plugins: The 5 W's and an H
Tom Jenkins
 
PDF
One commit, one release. Continuously delivering a Symfony project.
Javier López
 
PDF
Continous Delivering a PHP application
Javier López
 
PDF
vJUG - The JavaFX Ecosystem
Andres Almiray
 
PDF
Testing Automaton - CFSummit 2016
Ortus Solutions, Corp
 
PDF
Join the darkside: Selenium testing with Nightwatch.js
Seth McLaughlin
 
PDF
Никита Галкин "Testing in Frontend World"
Fwdays
 
PDF
Unit testing @ WordPress Meetup Tilburg 7 januari 2014
Barry Kooij
 
PDF
Codeception introduction and use in Yii
IlPeach
 
ODP
Integration Testing in Python
Panoptic Development, Inc.
 
Selenium Clinic Eurostar 2012 WebDriver Tutorial
Alan Richardson
 
Nightwatch at Tilt
Dave King
 
Testing Web Applications
Seth McLaughlin
 
Five Easy Ways to QA Your Drupal Site
Mediacurrent
 
Continuous Integration Testing in Django
Kevin Harvey
 
Automated Frontend Testing
Neil Crosby
 
Unit testing - A&BP CC
JWORKS powered by Ordina
 
Front-End Testing: Demystified
Seth McLaughlin
 
WebTest - Efficient Functional Web Testing with HtmlUnit and Beyond
mguillem
 
Unit-testing and E2E testing in JS
Michael Haberman
 
Unit testing plugins: The 5 W's and an H
Tom Jenkins
 
One commit, one release. Continuously delivering a Symfony project.
Javier López
 
Continous Delivering a PHP application
Javier López
 
vJUG - The JavaFX Ecosystem
Andres Almiray
 
Testing Automaton - CFSummit 2016
Ortus Solutions, Corp
 
Join the darkside: Selenium testing with Nightwatch.js
Seth McLaughlin
 
Никита Галкин "Testing in Frontend World"
Fwdays
 
Unit testing @ WordPress Meetup Tilburg 7 januari 2014
Barry Kooij
 
Codeception introduction and use in Yii
IlPeach
 
Integration Testing in Python
Panoptic Development, Inc.
 

Viewers also liked (20)

PDF
Hack the Future
Jason McCreary
 
PDF
Amp your site an intro to accelerated mobile pages
Robert McFrazier
 
PDF
php[world] 2015 Training - Laravel from the Ground Up
Joe Ferguson
 
PPTX
Engineer - Mastering the Art of Software
Cristiano Diniz da Silva
 
PDF
Zend Framework Foundations
Chuck Reeves
 
PDF
Adding 1.21 Gigawatts to Applications with RabbitMQ (Bulgaria PHP 2016 - Tuto...
James Titcumb
 
PDF
Dip Your Toes in the Sea of Security
James Titcumb
 
PDF
Console Apps: php artisan forthe:win
Joe Ferguson
 
PDF
Code Coverage for Total Security in Application Migrations
Dana Luther
 
PDF
Presentation Bulgaria PHP
Alena Holligan
 
PDF
Git Empowered
Jason McCreary
 
PPTX
Php extensions
Elizabeth Smith
 
PDF
Conscious Coupling
CiaranMcNulty
 
PDF
SunshinePHP 2017 - Making the most out of MySQL
Gabriela Ferrara
 
PPTX
Modern sql
Elizabeth Smith
 
PDF
200K+ reasons security is a must
Michelangelo van Dam
 
PDF
Intermediate OOP in PHP
David Stockton
 
PDF
PHP World DC 2015 - What Can Go Wrong with Agile Development and How to Fix It
Matt Toigo
 
PDF
Enough suffering, fix your architecture!
Luís Cobucci
 
PDF
Website Accessibility: It’s the Right Thing to do
DesignHammer
 
Hack the Future
Jason McCreary
 
Amp your site an intro to accelerated mobile pages
Robert McFrazier
 
php[world] 2015 Training - Laravel from the Ground Up
Joe Ferguson
 
Engineer - Mastering the Art of Software
Cristiano Diniz da Silva
 
Zend Framework Foundations
Chuck Reeves
 
Adding 1.21 Gigawatts to Applications with RabbitMQ (Bulgaria PHP 2016 - Tuto...
James Titcumb
 
Dip Your Toes in the Sea of Security
James Titcumb
 
Console Apps: php artisan forthe:win
Joe Ferguson
 
Code Coverage for Total Security in Application Migrations
Dana Luther
 
Presentation Bulgaria PHP
Alena Holligan
 
Git Empowered
Jason McCreary
 
Php extensions
Elizabeth Smith
 
Conscious Coupling
CiaranMcNulty
 
SunshinePHP 2017 - Making the most out of MySQL
Gabriela Ferrara
 
Modern sql
Elizabeth Smith
 
200K+ reasons security is a must
Michelangelo van Dam
 
Intermediate OOP in PHP
David Stockton
 
PHP World DC 2015 - What Can Go Wrong with Agile Development and How to Fix It
Matt Toigo
 
Enough suffering, fix your architecture!
Luís Cobucci
 
Website Accessibility: It’s the Right Thing to do
DesignHammer
 
Ad

Similar to Create, test, secure, repeat (20)

PDF
Fighting Fear-Driven-Development With PHPUnit
James Fuller
 
PDF
Leveling Up With Unit Testing - LonghornPHP 2022
Mark Niebergall
 
PDF
Your code are my tests
Michelangelo van Dam
 
PPTX
Test in action week 2
Yi-Huan Chan
 
PDF
PHPunit and you
markstory
 
KEY
Developer testing 101: Become a Testing Fanatic
LB Denker
 
PPT
Unit Testing using PHPUnit
varuntaliyan
 
PPT
Unit testing
davidahaskins
 
PDF
Php unit the-mostunknownparts
Bastian Feder
 
PDF
Test your code like a pro - PHPUnit in practice
Sebastian Marek
 
PDF
PhpUnit Best Practices
Edorian
 
ZIP
Test
Eddie Kao
 
PPTX
Unit Testng with PHP Unit - A Step by Step Training
Ram Awadh Prasad, PMP
 
KEY
Php Unit With Zend Framework Zendcon09
Michelangelo van Dam
 
KEY
PHPUnit testing to Zend_Test
Michelangelo van Dam
 
PDF
Introduction to Unit Testing with PHPUnit
Michelangelo van Dam
 
PPTX
PHPUnit: from zero to hero
Jeremy Cook
 
KEY
Developer testing 201: When to Mock and When to Integrate
LB Denker
 
PPTX
Php unit
Simona-Elena Stanescu
 
PDF
PhpUnit - The most unknown Parts
Bastian Feder
 
Fighting Fear-Driven-Development With PHPUnit
James Fuller
 
Leveling Up With Unit Testing - LonghornPHP 2022
Mark Niebergall
 
Your code are my tests
Michelangelo van Dam
 
Test in action week 2
Yi-Huan Chan
 
PHPunit and you
markstory
 
Developer testing 101: Become a Testing Fanatic
LB Denker
 
Unit Testing using PHPUnit
varuntaliyan
 
Unit testing
davidahaskins
 
Php unit the-mostunknownparts
Bastian Feder
 
Test your code like a pro - PHPUnit in practice
Sebastian Marek
 
PhpUnit Best Practices
Edorian
 
Test
Eddie Kao
 
Unit Testng with PHP Unit - A Step by Step Training
Ram Awadh Prasad, PMP
 
Php Unit With Zend Framework Zendcon09
Michelangelo van Dam
 
PHPUnit testing to Zend_Test
Michelangelo van Dam
 
Introduction to Unit Testing with PHPUnit
Michelangelo van Dam
 
PHPUnit: from zero to hero
Jeremy Cook
 
Developer testing 201: When to Mock and When to Integrate
LB Denker
 
PhpUnit - The most unknown Parts
Bastian Feder
 
Ad

More from Michelangelo van Dam (20)

PDF
GDPR Art. 25 - Privacy by design and default
Michelangelo van Dam
 
PDF
Moving from app services to azure functions
Michelangelo van Dam
 
PDF
Privacy by design
Michelangelo van Dam
 
PDF
DevOps or DevSecOps
Michelangelo van Dam
 
PDF
Privacy by design
Michelangelo van Dam
 
PDF
Continuous deployment 2.0
Michelangelo van Dam
 
PDF
Let your tests drive your code
Michelangelo van Dam
 
PDF
General Data Protection Regulation, a developer's story
Michelangelo van Dam
 
PDF
Leveraging a distributed architecture to your advantage
Michelangelo van Dam
 
PDF
The road to php 7.1
Michelangelo van Dam
 
PDF
Open source for a successful business
Michelangelo van Dam
 
PDF
Decouple your framework now, thank me later
Michelangelo van Dam
 
PDF
Deploy to azure in less then 15 minutes
Michelangelo van Dam
 
PDF
Azure and OSS, a match made in heaven
Michelangelo van Dam
 
PDF
Getting hands dirty with php7
Michelangelo van Dam
 
PDF
Zf2 how arrays will save your project
Michelangelo van Dam
 
PDF
The Continuous PHP Pipeline
Michelangelo van Dam
 
PDF
PHPUnit Episode iv.iii: Return of the tests
Michelangelo van Dam
 
PDF
Easily extend your existing php app with an api
Michelangelo van Dam
 
PDF
QA for PHP projects
Michelangelo van Dam
 
GDPR Art. 25 - Privacy by design and default
Michelangelo van Dam
 
Moving from app services to azure functions
Michelangelo van Dam
 
Privacy by design
Michelangelo van Dam
 
DevOps or DevSecOps
Michelangelo van Dam
 
Privacy by design
Michelangelo van Dam
 
Continuous deployment 2.0
Michelangelo van Dam
 
Let your tests drive your code
Michelangelo van Dam
 
General Data Protection Regulation, a developer's story
Michelangelo van Dam
 
Leveraging a distributed architecture to your advantage
Michelangelo van Dam
 
The road to php 7.1
Michelangelo van Dam
 
Open source for a successful business
Michelangelo van Dam
 
Decouple your framework now, thank me later
Michelangelo van Dam
 
Deploy to azure in less then 15 minutes
Michelangelo van Dam
 
Azure and OSS, a match made in heaven
Michelangelo van Dam
 
Getting hands dirty with php7
Michelangelo van Dam
 
Zf2 how arrays will save your project
Michelangelo van Dam
 
The Continuous PHP Pipeline
Michelangelo van Dam
 
PHPUnit Episode iv.iii: Return of the tests
Michelangelo van Dam
 
Easily extend your existing php app with an api
Michelangelo van Dam
 
QA for PHP projects
Michelangelo van Dam
 

Recently uploaded (20)

PPTX
The Role of Information Technology in Environmental Protectio....pptx
nallamillisriram
 
PPTX
Day2 B2 Best.pptx
helenjenefa1
 
PDF
Design Thinking basics for Engineers.pdf
CMR University
 
PPTX
原版一样(Acadia毕业证书)加拿大阿卡迪亚大学毕业证办理方法
Taqyea
 
PPTX
Element 11. ELECTRICITY safety and hazards
merrandomohandas
 
PPTX
MobileComputingMANET2023 MobileComputingMANET2023.pptx
masterfake98765
 
PPTX
GitOps_Repo_Structure for begeinner(Scaffolindg)
DanialHabibi2
 
PPTX
Evaluation and thermal analysis of shell and tube heat exchanger as per requi...
shahveer210504
 
PPTX
artificial intelligence applications in Geomatics
NawrasShatnawi1
 
PDF
MAD Unit - 2 Activity and Fragment Management in Android (Diploma IT)
JappanMavani
 
PDF
Introduction to Productivity and Quality
মোঃ ফুরকান উদ্দিন জুয়েল
 
PDF
Ethics and Trustworthy AI in Healthcare – Governing Sensitive Data, Profiling...
AlqualsaDIResearchGr
 
PDF
Zilliz Cloud Demo for performance and scale
Zilliz
 
PPTX
VITEEE 2026 Exam Details , Important Dates
SonaliSingh127098
 
PPTX
Lecture 1 Shell and Tube Heat exchanger-1.pptx
mailforillegalwork
 
PPTX
Shinkawa Proposal to meet Vibration API670.pptx
AchmadBashori2
 
PDF
PORTFOLIO Golam Kibria Khan — architect with a passion for thoughtful design...
MasumKhan59
 
PDF
Pressure Measurement training for engineers and Technicians
AIESOLUTIONS
 
PDF
MAD Unit - 1 Introduction of Android IT Department
JappanMavani
 
PPTX
Solar Thermal Energy System Seminar.pptx
Gpc Purapuza
 
The Role of Information Technology in Environmental Protectio....pptx
nallamillisriram
 
Day2 B2 Best.pptx
helenjenefa1
 
Design Thinking basics for Engineers.pdf
CMR University
 
原版一样(Acadia毕业证书)加拿大阿卡迪亚大学毕业证办理方法
Taqyea
 
Element 11. ELECTRICITY safety and hazards
merrandomohandas
 
MobileComputingMANET2023 MobileComputingMANET2023.pptx
masterfake98765
 
GitOps_Repo_Structure for begeinner(Scaffolindg)
DanialHabibi2
 
Evaluation and thermal analysis of shell and tube heat exchanger as per requi...
shahveer210504
 
artificial intelligence applications in Geomatics
NawrasShatnawi1
 
MAD Unit - 2 Activity and Fragment Management in Android (Diploma IT)
JappanMavani
 
Introduction to Productivity and Quality
মোঃ ফুরকান উদ্দিন জুয়েল
 
Ethics and Trustworthy AI in Healthcare – Governing Sensitive Data, Profiling...
AlqualsaDIResearchGr
 
Zilliz Cloud Demo for performance and scale
Zilliz
 
VITEEE 2026 Exam Details , Important Dates
SonaliSingh127098
 
Lecture 1 Shell and Tube Heat exchanger-1.pptx
mailforillegalwork
 
Shinkawa Proposal to meet Vibration API670.pptx
AchmadBashori2
 
PORTFOLIO Golam Kibria Khan — architect with a passion for thoughtful design...
MasumKhan59
 
Pressure Measurement training for engineers and Technicians
AIESOLUTIONS
 
MAD Unit - 1 Introduction of Android IT Department
JappanMavani
 
Solar Thermal Energy System Seminar.pptx
Gpc Purapuza
 

Create, test, secure, repeat