SlideShare a Scribd company logo
1 of 177
Download to read offline
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);
    }
}
@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
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
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
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
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
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 In2itWorkshopCtsr;
class SampleClass
{
    public function doSomething($argument)
    {
        return 'Hello ' . $argument . '!';
    }
}
Update further
<?php
namespace In2itWorkshopCtsr;
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
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);
    }
}
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'
);
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');
}
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
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);
    }
}
<?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);
}
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);
}
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 ();
}
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');
}
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');
}
/**
 * @covers ModuleManager::include_install
 */
public function testModuleManagerCanLoadMailModule()
{
    $result = ModuleManager::include_install('Mail');
    $this->assertTrue($result);
}
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
                );
            }
        }
    }
    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 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
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

Selenium Clinic Eurostar 2012 WebDriver Tutorial
Selenium Clinic Eurostar 2012 WebDriver TutorialSelenium Clinic Eurostar 2012 WebDriver Tutorial
Selenium Clinic Eurostar 2012 WebDriver TutorialAlan Richardson
 
Nightwatch at Tilt
Nightwatch at TiltNightwatch at Tilt
Nightwatch at TiltDave King
 
Testing Web Applications
Testing Web ApplicationsTesting Web Applications
Testing Web ApplicationsSeth McLaughlin
 
Five Easy Ways to QA Your Drupal Site
Five Easy Ways to QA Your Drupal SiteFive Easy Ways to QA Your Drupal Site
Five Easy Ways to QA Your Drupal SiteMediacurrent
 
Continuous Integration Testing in Django
Continuous Integration Testing in DjangoContinuous Integration Testing in Django
Continuous Integration Testing in DjangoKevin Harvey
 
Automated Frontend Testing
Automated Frontend TestingAutomated Frontend Testing
Automated Frontend TestingNeil Crosby
 
Front-End Testing: Demystified
Front-End Testing: DemystifiedFront-End Testing: Demystified
Front-End Testing: DemystifiedSeth McLaughlin
 
WebTest - Efficient Functional Web Testing with HtmlUnit and Beyond
WebTest - Efficient Functional Web Testing with HtmlUnit and BeyondWebTest - Efficient Functional Web Testing with HtmlUnit and Beyond
WebTest - Efficient Functional Web Testing with HtmlUnit and Beyondmguillem
 
Unit-testing and E2E testing in JS
Unit-testing and E2E testing in JSUnit-testing and E2E testing in JS
Unit-testing and E2E testing in JSMichael Haberman
 
Unit testing plugins: The 5 W's and an H
Unit testing plugins: The 5 W's and an HUnit testing plugins: The 5 W's and an H
Unit testing plugins: The 5 W's and an HTom Jenkins
 
One commit, one release. Continuously delivering a Symfony project.
One commit, one release. Continuously delivering a Symfony project.One commit, one release. Continuously delivering a Symfony project.
One commit, one release. Continuously delivering a Symfony project.Javier López
 
Continous Delivering a PHP application
Continous Delivering a PHP applicationContinous Delivering a PHP application
Continous Delivering a PHP applicationJavier López
 
vJUG - The JavaFX Ecosystem
vJUG - The JavaFX EcosystemvJUG - The JavaFX Ecosystem
vJUG - The JavaFX EcosystemAndres Almiray
 
Join the darkside: Selenium testing with Nightwatch.js
Join the darkside: Selenium testing with Nightwatch.jsJoin the darkside: Selenium testing with Nightwatch.js
Join the darkside: Selenium testing with Nightwatch.jsSeth McLaughlin
 
Никита Галкин "Testing in Frontend World"
Никита Галкин "Testing in Frontend World"Никита Галкин "Testing in Frontend World"
Никита Галкин "Testing in Frontend World"Fwdays
 
Unit testing @ WordPress Meetup Tilburg 7 januari 2014
Unit testing @ WordPress Meetup Tilburg 7 januari 2014Unit testing @ WordPress Meetup Tilburg 7 januari 2014
Unit testing @ WordPress Meetup Tilburg 7 januari 2014Barry Kooij
 
Codeception introduction and use in Yii
Codeception introduction and use in YiiCodeception introduction and use in Yii
Codeception introduction and use in YiiIlPeach
 

What's hot (20)

Selenium Clinic Eurostar 2012 WebDriver Tutorial
Selenium Clinic Eurostar 2012 WebDriver TutorialSelenium Clinic Eurostar 2012 WebDriver Tutorial
Selenium Clinic Eurostar 2012 WebDriver Tutorial
 
Nightwatch at Tilt
Nightwatch at TiltNightwatch at Tilt
Nightwatch at Tilt
 
Testing Web Applications
Testing Web ApplicationsTesting Web Applications
Testing Web Applications
 
Five Easy Ways to QA Your Drupal Site
Five Easy Ways to QA Your Drupal SiteFive Easy Ways to QA Your Drupal Site
Five Easy Ways to QA Your Drupal Site
 
Continuous Integration Testing in Django
Continuous Integration Testing in DjangoContinuous Integration Testing in Django
Continuous Integration Testing in Django
 
Automated Frontend Testing
Automated Frontend TestingAutomated Frontend Testing
Automated Frontend Testing
 
Unit testing - A&BP CC
Unit testing - A&BP CCUnit testing - A&BP CC
Unit testing - A&BP CC
 
Front-End Testing: Demystified
Front-End Testing: DemystifiedFront-End Testing: Demystified
Front-End Testing: Demystified
 
WebTest - Efficient Functional Web Testing with HtmlUnit and Beyond
WebTest - Efficient Functional Web Testing with HtmlUnit and BeyondWebTest - Efficient Functional Web Testing with HtmlUnit and Beyond
WebTest - Efficient Functional Web Testing with HtmlUnit and Beyond
 
Unit-testing and E2E testing in JS
Unit-testing and E2E testing in JSUnit-testing and E2E testing in JS
Unit-testing and E2E testing in JS
 
Unit testing plugins: The 5 W's and an H
Unit testing plugins: The 5 W's and an HUnit testing plugins: The 5 W's and an H
Unit testing plugins: The 5 W's and an H
 
One commit, one release. Continuously delivering a Symfony project.
One commit, one release. Continuously delivering a Symfony project.One commit, one release. Continuously delivering a Symfony project.
One commit, one release. Continuously delivering a Symfony project.
 
Continous Delivering a PHP application
Continous Delivering a PHP applicationContinous Delivering a PHP application
Continous Delivering a PHP application
 
vJUG - The JavaFX Ecosystem
vJUG - The JavaFX EcosystemvJUG - The JavaFX Ecosystem
vJUG - The JavaFX Ecosystem
 
Testing Automaton - CFSummit 2016
Testing Automaton - CFSummit 2016Testing Automaton - CFSummit 2016
Testing Automaton - CFSummit 2016
 
Join the darkside: Selenium testing with Nightwatch.js
Join the darkside: Selenium testing with Nightwatch.jsJoin the darkside: Selenium testing with Nightwatch.js
Join the darkside: Selenium testing with Nightwatch.js
 
Никита Галкин "Testing in Frontend World"
Никита Галкин "Testing in Frontend World"Никита Галкин "Testing in Frontend World"
Никита Галкин "Testing in Frontend World"
 
Unit testing @ WordPress Meetup Tilburg 7 januari 2014
Unit testing @ WordPress Meetup Tilburg 7 januari 2014Unit testing @ WordPress Meetup Tilburg 7 januari 2014
Unit testing @ WordPress Meetup Tilburg 7 januari 2014
 
Codeception introduction and use in Yii
Codeception introduction and use in YiiCodeception introduction and use in Yii
Codeception introduction and use in Yii
 
Integration Testing in Python
Integration Testing in PythonIntegration Testing in Python
Integration Testing in Python
 

Viewers also liked

Amp your site an intro to accelerated mobile pages
Amp your site  an intro to accelerated mobile pagesAmp your site  an intro to accelerated mobile pages
Amp your site an intro to accelerated mobile pagesRobert McFrazier
 
php[world] 2015 Training - Laravel from the Ground Up
php[world] 2015 Training - Laravel from the Ground Upphp[world] 2015 Training - Laravel from the Ground Up
php[world] 2015 Training - Laravel from the Ground UpJoe Ferguson
 
Zend Framework Foundations
Zend Framework FoundationsZend Framework Foundations
Zend Framework FoundationsChuck Reeves
 
Adding 1.21 Gigawatts to Applications with RabbitMQ (Bulgaria PHP 2016 - Tuto...
Adding 1.21 Gigawatts to Applications with RabbitMQ (Bulgaria PHP 2016 - Tuto...Adding 1.21 Gigawatts to Applications with RabbitMQ (Bulgaria PHP 2016 - Tuto...
Adding 1.21 Gigawatts to Applications with RabbitMQ (Bulgaria PHP 2016 - Tuto...James Titcumb
 
Dip Your Toes in the Sea of Security
Dip Your Toes in the Sea of SecurityDip Your Toes in the Sea of Security
Dip Your Toes in the Sea of SecurityJames Titcumb
 
Console Apps: php artisan forthe:win
Console Apps: php artisan forthe:win Console Apps: php artisan forthe:win
Console Apps: php artisan forthe:win Joe Ferguson
 
Code Coverage for Total Security in Application Migrations
Code Coverage for Total Security in Application MigrationsCode Coverage for Total Security in Application Migrations
Code Coverage for Total Security in Application MigrationsDana Luther
 
Presentation Bulgaria PHP
Presentation Bulgaria PHPPresentation Bulgaria PHP
Presentation Bulgaria PHPAlena Holligan
 
SunshinePHP 2017 - Making the most out of MySQL
SunshinePHP 2017 - Making the most out of MySQLSunshinePHP 2017 - Making the most out of MySQL
SunshinePHP 2017 - Making the most out of MySQLGabriela Ferrara
 
Intermediate OOP in PHP
Intermediate OOP in PHPIntermediate OOP in PHP
Intermediate OOP in PHPDavid Stockton
 
PHP World DC 2015 - What Can Go Wrong with Agile Development and How to Fix It
PHP World DC 2015 - What Can Go Wrong with Agile Development and How to Fix ItPHP World DC 2015 - What Can Go Wrong with Agile Development and How to Fix It
PHP World DC 2015 - What Can Go Wrong with Agile Development and How to Fix ItMatt Toigo
 
Enough suffering, fix your architecture!
Enough suffering, fix your architecture!Enough suffering, fix your architecture!
Enough suffering, fix your architecture!Luís Cobucci
 
Website Accessibility: It’s the Right Thing to do
Website Accessibility: It’s the Right Thing to doWebsite Accessibility: It’s the Right Thing to do
Website Accessibility: It’s the Right Thing to doDesignHammer
 

Viewers also liked (20)

Hack the Future
Hack the FutureHack the Future
Hack the Future
 
Amp your site an intro to accelerated mobile pages
Amp your site  an intro to accelerated mobile pagesAmp your site  an intro to accelerated mobile pages
Amp your site an intro to accelerated mobile pages
 
php[world] 2015 Training - Laravel from the Ground Up
php[world] 2015 Training - Laravel from the Ground Upphp[world] 2015 Training - Laravel from the Ground Up
php[world] 2015 Training - Laravel from the Ground Up
 
Engineer - Mastering the Art of Software
Engineer - Mastering the Art of SoftwareEngineer - Mastering the Art of Software
Engineer - Mastering the Art of Software
 
Zend Framework Foundations
Zend Framework FoundationsZend Framework Foundations
Zend Framework Foundations
 
Adding 1.21 Gigawatts to Applications with RabbitMQ (Bulgaria PHP 2016 - Tuto...
Adding 1.21 Gigawatts to Applications with RabbitMQ (Bulgaria PHP 2016 - Tuto...Adding 1.21 Gigawatts to Applications with RabbitMQ (Bulgaria PHP 2016 - Tuto...
Adding 1.21 Gigawatts to Applications with RabbitMQ (Bulgaria PHP 2016 - Tuto...
 
Dip Your Toes in the Sea of Security
Dip Your Toes in the Sea of SecurityDip Your Toes in the Sea of Security
Dip Your Toes in the Sea of Security
 
Console Apps: php artisan forthe:win
Console Apps: php artisan forthe:win Console Apps: php artisan forthe:win
Console Apps: php artisan forthe:win
 
Code Coverage for Total Security in Application Migrations
Code Coverage for Total Security in Application MigrationsCode Coverage for Total Security in Application Migrations
Code Coverage for Total Security in Application Migrations
 
Presentation Bulgaria PHP
Presentation Bulgaria PHPPresentation Bulgaria PHP
Presentation Bulgaria PHP
 
Git Empowered
Git EmpoweredGit Empowered
Git Empowered
 
Php extensions
Php extensionsPhp extensions
Php extensions
 
Conscious Coupling
Conscious CouplingConscious Coupling
Conscious Coupling
 
SunshinePHP 2017 - Making the most out of MySQL
SunshinePHP 2017 - Making the most out of MySQLSunshinePHP 2017 - Making the most out of MySQL
SunshinePHP 2017 - Making the most out of MySQL
 
Modern sql
Modern sqlModern sql
Modern sql
 
200K+ reasons security is a must
200K+ reasons security is a must200K+ reasons security is a must
200K+ reasons security is a must
 
Intermediate OOP in PHP
Intermediate OOP in PHPIntermediate OOP in PHP
Intermediate OOP in PHP
 
PHP World DC 2015 - What Can Go Wrong with Agile Development and How to Fix It
PHP World DC 2015 - What Can Go Wrong with Agile Development and How to Fix ItPHP World DC 2015 - What Can Go Wrong with Agile Development and How to Fix It
PHP World DC 2015 - What Can Go Wrong with Agile Development and How to Fix It
 
Enough suffering, fix your architecture!
Enough suffering, fix your architecture!Enough suffering, fix your architecture!
Enough suffering, fix your architecture!
 
Website Accessibility: It’s the Right Thing to do
Website Accessibility: It’s the Right Thing to doWebsite Accessibility: It’s the Right Thing to do
Website Accessibility: It’s the Right Thing to do
 

Similar to Create, Test, Secure Code with PHPUnit

Fighting Fear-Driven-Development With PHPUnit
Fighting Fear-Driven-Development With PHPUnitFighting Fear-Driven-Development With PHPUnit
Fighting Fear-Driven-Development With PHPUnitJames Fuller
 
Testing NodeJS with Mocha, Should, Sinon, and JSCoverage
Testing NodeJS with Mocha, Should, Sinon, and JSCoverageTesting NodeJS with Mocha, Should, Sinon, and JSCoverage
Testing NodeJS with Mocha, Should, Sinon, and JSCoveragemlilley
 
Leveling Up With Unit Testing - php[tek] 2023
Leveling Up With Unit Testing - php[tek] 2023Leveling Up With Unit Testing - php[tek] 2023
Leveling Up With Unit Testing - php[tek] 2023Mark Niebergall
 
Performance and Scalability Testing with Python and Multi-Mechanize
Performance and Scalability Testing with Python and Multi-MechanizePerformance and Scalability Testing with Python and Multi-Mechanize
Performance and Scalability Testing with Python and Multi-Mechanizecoreygoldberg
 
Automatic testing and quality assurance for WordPress plugins and themes
Automatic testing and quality assurance for WordPress plugins and themesAutomatic testing and quality assurance for WordPress plugins and themes
Automatic testing and quality assurance for WordPress plugins and themesOtto Kekäläinen
 
RichFaces - Testing on Mobile Devices
RichFaces - Testing on Mobile DevicesRichFaces - Testing on Mobile Devices
RichFaces - Testing on Mobile DevicesPavol Pitoňák
 
Real World Lessons on the Pain Points of Node.js Applications
Real World Lessons on the Pain Points of Node.js ApplicationsReal World Lessons on the Pain Points of Node.js Applications
Real World Lessons on the Pain Points of Node.js ApplicationsBen Hall
 
Vagrant + Rouster at salesforce.com - PuppetConf 2013
Vagrant + Rouster at salesforce.com - PuppetConf 2013Vagrant + Rouster at salesforce.com - PuppetConf 2013
Vagrant + Rouster at salesforce.com - PuppetConf 2013Puppet
 
Unit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMockUnit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMockRobot Media
 
Adopt DevOps philosophy on your Symfony projects (Symfony Live 2011)
Adopt DevOps philosophy on your Symfony projects (Symfony Live 2011)Adopt DevOps philosophy on your Symfony projects (Symfony Live 2011)
Adopt DevOps philosophy on your Symfony projects (Symfony Live 2011)Fabrice Bernhard
 
Test Driven Development with JavaFX
Test Driven Development with JavaFXTest Driven Development with JavaFX
Test Driven Development with JavaFXHendrik Ebbers
 
Making the most of your Test Suite
Making the most of your Test SuiteMaking the most of your Test Suite
Making the most of your Test Suiteericholscher
 
Getting started with spfx
Getting started with spfxGetting started with spfx
Getting started with spfxJenkins NS
 
Vagrant+Rouster at salesforce.com
Vagrant+Rouster at salesforce.comVagrant+Rouster at salesforce.com
Vagrant+Rouster at salesforce.comchorankates
 
Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12Michelangelo van Dam
 
Leveling Up With Unit Testing - LonghornPHP 2022
Leveling Up With Unit Testing - LonghornPHP 2022Leveling Up With Unit Testing - LonghornPHP 2022
Leveling Up With Unit Testing - LonghornPHP 2022Mark Niebergall
 
How to test infrastructure code: automated testing for Terraform, Kubernetes,...
How to test infrastructure code: automated testing for Terraform, Kubernetes,...How to test infrastructure code: automated testing for Terraform, Kubernetes,...
How to test infrastructure code: automated testing for Terraform, Kubernetes,...Yevgeniy Brikman
 
Introduction to PowerShell
Introduction to PowerShellIntroduction to PowerShell
Introduction to PowerShellBoulos Dib
 

Similar to Create, Test, Secure Code with PHPUnit (20)

Fighting Fear-Driven-Development With PHPUnit
Fighting Fear-Driven-Development With PHPUnitFighting Fear-Driven-Development With PHPUnit
Fighting Fear-Driven-Development With PHPUnit
 
Testing NodeJS with Mocha, Should, Sinon, and JSCoverage
Testing NodeJS with Mocha, Should, Sinon, and JSCoverageTesting NodeJS with Mocha, Should, Sinon, and JSCoverage
Testing NodeJS with Mocha, Should, Sinon, and JSCoverage
 
Leveling Up With Unit Testing - php[tek] 2023
Leveling Up With Unit Testing - php[tek] 2023Leveling Up With Unit Testing - php[tek] 2023
Leveling Up With Unit Testing - php[tek] 2023
 
Performance and Scalability Testing with Python and Multi-Mechanize
Performance and Scalability Testing with Python and Multi-MechanizePerformance and Scalability Testing with Python and Multi-Mechanize
Performance and Scalability Testing with Python and Multi-Mechanize
 
Automatic testing and quality assurance for WordPress plugins and themes
Automatic testing and quality assurance for WordPress plugins and themesAutomatic testing and quality assurance for WordPress plugins and themes
Automatic testing and quality assurance for WordPress plugins and themes
 
RichFaces - Testing on Mobile Devices
RichFaces - Testing on Mobile DevicesRichFaces - Testing on Mobile Devices
RichFaces - Testing on Mobile Devices
 
Real World Lessons on the Pain Points of Node.js Applications
Real World Lessons on the Pain Points of Node.js ApplicationsReal World Lessons on the Pain Points of Node.js Applications
Real World Lessons on the Pain Points of Node.js Applications
 
Vagrant + Rouster at salesforce.com - PuppetConf 2013
Vagrant + Rouster at salesforce.com - PuppetConf 2013Vagrant + Rouster at salesforce.com - PuppetConf 2013
Vagrant + Rouster at salesforce.com - PuppetConf 2013
 
Unit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMockUnit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMock
 
Adopt DevOps philosophy on your Symfony projects (Symfony Live 2011)
Adopt DevOps philosophy on your Symfony projects (Symfony Live 2011)Adopt DevOps philosophy on your Symfony projects (Symfony Live 2011)
Adopt DevOps philosophy on your Symfony projects (Symfony Live 2011)
 
The JavaFX Ecosystem
The JavaFX EcosystemThe JavaFX Ecosystem
The JavaFX Ecosystem
 
Test Driven Development with JavaFX
Test Driven Development with JavaFXTest Driven Development with JavaFX
Test Driven Development with JavaFX
 
Making the most of your Test Suite
Making the most of your Test SuiteMaking the most of your Test Suite
Making the most of your Test Suite
 
Getting started with spfx
Getting started with spfxGetting started with spfx
Getting started with spfx
 
Vagrant+Rouster at salesforce.com
Vagrant+Rouster at salesforce.comVagrant+Rouster at salesforce.com
Vagrant+Rouster at salesforce.com
 
SmokeTests
SmokeTestsSmokeTests
SmokeTests
 
Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12
 
Leveling Up With Unit Testing - LonghornPHP 2022
Leveling Up With Unit Testing - LonghornPHP 2022Leveling Up With Unit Testing - LonghornPHP 2022
Leveling Up With Unit Testing - LonghornPHP 2022
 
How to test infrastructure code: automated testing for Terraform, Kubernetes,...
How to test infrastructure code: automated testing for Terraform, Kubernetes,...How to test infrastructure code: automated testing for Terraform, Kubernetes,...
How to test infrastructure code: automated testing for Terraform, Kubernetes,...
 
Introduction to PowerShell
Introduction to PowerShellIntroduction to PowerShell
Introduction to PowerShell
 

More from Michelangelo van Dam

GDPR Art. 25 - Privacy by design and default
GDPR Art. 25 - Privacy by design and defaultGDPR Art. 25 - Privacy by design and default
GDPR Art. 25 - Privacy by design and defaultMichelangelo van Dam
 
Moving from app services to azure functions
Moving from app services to azure functionsMoving from app services to azure functions
Moving from app services to azure functionsMichelangelo van Dam
 
General Data Protection Regulation, a developer's story
General Data Protection Regulation, a developer's storyGeneral Data Protection Regulation, a developer's story
General Data Protection Regulation, a developer's storyMichelangelo van Dam
 
Leveraging a distributed architecture to your advantage
Leveraging a distributed architecture to your advantageLeveraging a distributed architecture to your advantage
Leveraging a distributed architecture to your advantageMichelangelo van Dam
 
Open source for a successful business
Open source for a successful businessOpen source for a successful business
Open source for a successful businessMichelangelo van Dam
 
Decouple your framework now, thank me later
Decouple your framework now, thank me laterDecouple your framework now, thank me later
Decouple your framework now, thank me laterMichelangelo van Dam
 
Deploy to azure in less then 15 minutes
Deploy to azure in less then 15 minutesDeploy to azure in less then 15 minutes
Deploy to azure in less then 15 minutesMichelangelo van Dam
 
Azure and OSS, a match made in heaven
Azure and OSS, a match made in heavenAzure and OSS, a match made in heaven
Azure and OSS, a match made in heavenMichelangelo van Dam
 
Zf2 how arrays will save your project
Zf2   how arrays will save your projectZf2   how arrays will save your project
Zf2 how arrays will save your projectMichelangelo van Dam
 
PHPUnit Episode iv.iii: Return of the tests
PHPUnit Episode iv.iii: Return of the testsPHPUnit Episode iv.iii: Return of the tests
PHPUnit Episode iv.iii: Return of the testsMichelangelo van Dam
 
Easily extend your existing php app with an api
Easily extend your existing php app with an apiEasily extend your existing php app with an api
Easily extend your existing php app with an apiMichelangelo van Dam
 

More from Michelangelo van Dam (20)

GDPR Art. 25 - Privacy by design and default
GDPR Art. 25 - Privacy by design and defaultGDPR Art. 25 - Privacy by design and default
GDPR Art. 25 - Privacy by design and default
 
Moving from app services to azure functions
Moving from app services to azure functionsMoving from app services to azure functions
Moving from app services to azure functions
 
Privacy by design
Privacy by designPrivacy by design
Privacy by design
 
DevOps or DevSecOps
DevOps or DevSecOpsDevOps or DevSecOps
DevOps or DevSecOps
 
Privacy by design
Privacy by designPrivacy by design
Privacy by design
 
Continuous deployment 2.0
Continuous deployment 2.0Continuous deployment 2.0
Continuous deployment 2.0
 
Let your tests drive your code
Let your tests drive your codeLet your tests drive your code
Let your tests drive your code
 
General Data Protection Regulation, a developer's story
General Data Protection Regulation, a developer's storyGeneral Data Protection Regulation, a developer's story
General Data Protection Regulation, a developer's story
 
Leveraging a distributed architecture to your advantage
Leveraging a distributed architecture to your advantageLeveraging a distributed architecture to your advantage
Leveraging a distributed architecture to your advantage
 
The road to php 7.1
The road to php 7.1The road to php 7.1
The road to php 7.1
 
Open source for a successful business
Open source for a successful businessOpen source for a successful business
Open source for a successful business
 
Decouple your framework now, thank me later
Decouple your framework now, thank me laterDecouple your framework now, thank me later
Decouple your framework now, thank me later
 
Deploy to azure in less then 15 minutes
Deploy to azure in less then 15 minutesDeploy to azure in less then 15 minutes
Deploy to azure in less then 15 minutes
 
Azure and OSS, a match made in heaven
Azure and OSS, a match made in heavenAzure and OSS, a match made in heaven
Azure and OSS, a match made in heaven
 
Getting hands dirty with php7
Getting hands dirty with php7Getting hands dirty with php7
Getting hands dirty with php7
 
Zf2 how arrays will save your project
Zf2   how arrays will save your projectZf2   how arrays will save your project
Zf2 how arrays will save your project
 
The Continuous PHP Pipeline
The Continuous PHP PipelineThe Continuous PHP Pipeline
The Continuous PHP Pipeline
 
PHPUnit Episode iv.iii: Return of the tests
PHPUnit Episode iv.iii: Return of the testsPHPUnit Episode iv.iii: Return of the tests
PHPUnit Episode iv.iii: Return of the tests
 
Easily extend your existing php app with an api
Easily extend your existing php app with an apiEasily extend your existing php app with an api
Easily extend your existing php app with an api
 
Your code are my tests
Your code are my testsYour code are my tests
Your code are my tests
 

Recently uploaded

BSNL Internship Training presentation.pptx
BSNL Internship Training presentation.pptxBSNL Internship Training presentation.pptx
BSNL Internship Training presentation.pptxNiranjanYadav41
 
IVE Industry Focused Event - Defence Sector 2024
IVE Industry Focused Event - Defence Sector 2024IVE Industry Focused Event - Defence Sector 2024
IVE Industry Focused Event - Defence Sector 2024Mark Billinghurst
 
welding defects observed during the welding
welding defects observed during the weldingwelding defects observed during the welding
welding defects observed during the weldingMuhammadUzairLiaqat
 
Earthing details of Electrical Substation
Earthing details of Electrical SubstationEarthing details of Electrical Substation
Earthing details of Electrical Substationstephanwindworld
 
National Level Hackathon Participation Certificate.pdf
National Level Hackathon Participation Certificate.pdfNational Level Hackathon Participation Certificate.pdf
National Level Hackathon Participation Certificate.pdfRajuKanojiya4
 
11. Properties of Liquid Fuels in Energy Engineering.pdf
11. Properties of Liquid Fuels in Energy Engineering.pdf11. Properties of Liquid Fuels in Energy Engineering.pdf
11. Properties of Liquid Fuels in Energy Engineering.pdfHafizMudaserAhmad
 
Katarzyna Lipka-Sidor - BIM School Course
Katarzyna Lipka-Sidor - BIM School CourseKatarzyna Lipka-Sidor - BIM School Course
Katarzyna Lipka-Sidor - BIM School Coursebim.edu.pl
 
Arduino_CSE ece ppt for working and principal of arduino.ppt
Arduino_CSE ece ppt for working and principal of arduino.pptArduino_CSE ece ppt for working and principal of arduino.ppt
Arduino_CSE ece ppt for working and principal of arduino.pptSAURABHKUMAR892774
 
Unit7-DC_Motors nkkjnsdkfnfcdfknfdgfggfg
Unit7-DC_Motors nkkjnsdkfnfcdfknfdgfggfgUnit7-DC_Motors nkkjnsdkfnfcdfknfdgfggfg
Unit7-DC_Motors nkkjnsdkfnfcdfknfdgfggfgsaravananr517913
 
Ch10-Global Supply Chain - Cadena de Suministro.pdf
Ch10-Global Supply Chain - Cadena de Suministro.pdfCh10-Global Supply Chain - Cadena de Suministro.pdf
Ch10-Global Supply Chain - Cadena de Suministro.pdfChristianCDAM
 
Engineering Drawing section of solid
Engineering Drawing     section of solidEngineering Drawing     section of solid
Engineering Drawing section of solidnamansinghjarodiya
 
Crushers to screens in aggregate production
Crushers to screens in aggregate productionCrushers to screens in aggregate production
Crushers to screens in aggregate productionChinnuNinan
 
DM Pillar Training Manual.ppt will be useful in deploying TPM in project
DM Pillar Training Manual.ppt will be useful in deploying TPM in projectDM Pillar Training Manual.ppt will be useful in deploying TPM in project
DM Pillar Training Manual.ppt will be useful in deploying TPM in projectssuserb6619e
 
Class 1 | NFPA 72 | Overview Fire Alarm System
Class 1 | NFPA 72 | Overview Fire Alarm SystemClass 1 | NFPA 72 | Overview Fire Alarm System
Class 1 | NFPA 72 | Overview Fire Alarm Systemirfanmechengr
 
home automation using Arduino by Aditya Prasad
home automation using Arduino by Aditya Prasadhome automation using Arduino by Aditya Prasad
home automation using Arduino by Aditya Prasadaditya806802
 
Autonomous emergency braking system (aeb) ppt.ppt
Autonomous emergency braking system (aeb) ppt.pptAutonomous emergency braking system (aeb) ppt.ppt
Autonomous emergency braking system (aeb) ppt.pptbibisarnayak0
 
Gurgaon ✡️9711147426✨Call In girls Gurgaon Sector 51 escort service
Gurgaon ✡️9711147426✨Call In girls Gurgaon Sector 51 escort serviceGurgaon ✡️9711147426✨Call In girls Gurgaon Sector 51 escort service
Gurgaon ✡️9711147426✨Call In girls Gurgaon Sector 51 escort servicejennyeacort
 
Energy Awareness training ppt for manufacturing process.pptx
Energy Awareness training ppt for manufacturing process.pptxEnergy Awareness training ppt for manufacturing process.pptx
Energy Awareness training ppt for manufacturing process.pptxsiddharthjain2303
 

Recently uploaded (20)

BSNL Internship Training presentation.pptx
BSNL Internship Training presentation.pptxBSNL Internship Training presentation.pptx
BSNL Internship Training presentation.pptx
 
IVE Industry Focused Event - Defence Sector 2024
IVE Industry Focused Event - Defence Sector 2024IVE Industry Focused Event - Defence Sector 2024
IVE Industry Focused Event - Defence Sector 2024
 
welding defects observed during the welding
welding defects observed during the weldingwelding defects observed during the welding
welding defects observed during the welding
 
Earthing details of Electrical Substation
Earthing details of Electrical SubstationEarthing details of Electrical Substation
Earthing details of Electrical Substation
 
National Level Hackathon Participation Certificate.pdf
National Level Hackathon Participation Certificate.pdfNational Level Hackathon Participation Certificate.pdf
National Level Hackathon Participation Certificate.pdf
 
11. Properties of Liquid Fuels in Energy Engineering.pdf
11. Properties of Liquid Fuels in Energy Engineering.pdf11. Properties of Liquid Fuels in Energy Engineering.pdf
11. Properties of Liquid Fuels in Energy Engineering.pdf
 
Katarzyna Lipka-Sidor - BIM School Course
Katarzyna Lipka-Sidor - BIM School CourseKatarzyna Lipka-Sidor - BIM School Course
Katarzyna Lipka-Sidor - BIM School Course
 
Arduino_CSE ece ppt for working and principal of arduino.ppt
Arduino_CSE ece ppt for working and principal of arduino.pptArduino_CSE ece ppt for working and principal of arduino.ppt
Arduino_CSE ece ppt for working and principal of arduino.ppt
 
Unit7-DC_Motors nkkjnsdkfnfcdfknfdgfggfg
Unit7-DC_Motors nkkjnsdkfnfcdfknfdgfggfgUnit7-DC_Motors nkkjnsdkfnfcdfknfdgfggfg
Unit7-DC_Motors nkkjnsdkfnfcdfknfdgfggfg
 
Ch10-Global Supply Chain - Cadena de Suministro.pdf
Ch10-Global Supply Chain - Cadena de Suministro.pdfCh10-Global Supply Chain - Cadena de Suministro.pdf
Ch10-Global Supply Chain - Cadena de Suministro.pdf
 
Engineering Drawing section of solid
Engineering Drawing     section of solidEngineering Drawing     section of solid
Engineering Drawing section of solid
 
Design and analysis of solar grass cutter.pdf
Design and analysis of solar grass cutter.pdfDesign and analysis of solar grass cutter.pdf
Design and analysis of solar grass cutter.pdf
 
Crushers to screens in aggregate production
Crushers to screens in aggregate productionCrushers to screens in aggregate production
Crushers to screens in aggregate production
 
DM Pillar Training Manual.ppt will be useful in deploying TPM in project
DM Pillar Training Manual.ppt will be useful in deploying TPM in projectDM Pillar Training Manual.ppt will be useful in deploying TPM in project
DM Pillar Training Manual.ppt will be useful in deploying TPM in project
 
Class 1 | NFPA 72 | Overview Fire Alarm System
Class 1 | NFPA 72 | Overview Fire Alarm SystemClass 1 | NFPA 72 | Overview Fire Alarm System
Class 1 | NFPA 72 | Overview Fire Alarm System
 
POWER SYSTEMS-1 Complete notes examples
POWER SYSTEMS-1 Complete notes  examplesPOWER SYSTEMS-1 Complete notes  examples
POWER SYSTEMS-1 Complete notes examples
 
home automation using Arduino by Aditya Prasad
home automation using Arduino by Aditya Prasadhome automation using Arduino by Aditya Prasad
home automation using Arduino by Aditya Prasad
 
Autonomous emergency braking system (aeb) ppt.ppt
Autonomous emergency braking system (aeb) ppt.pptAutonomous emergency braking system (aeb) ppt.ppt
Autonomous emergency braking system (aeb) ppt.ppt
 
Gurgaon ✡️9711147426✨Call In girls Gurgaon Sector 51 escort service
Gurgaon ✡️9711147426✨Call In girls Gurgaon Sector 51 escort serviceGurgaon ✡️9711147426✨Call In girls Gurgaon Sector 51 escort service
Gurgaon ✡️9711147426✨Call In girls Gurgaon Sector 51 escort service
 
Energy Awareness training ppt for manufacturing process.pptx
Energy Awareness training ppt for manufacturing process.pptxEnergy Awareness training ppt for manufacturing process.pptx
Energy Awareness training ppt for manufacturing process.pptx
 

Create, Test, Secure Code with PHPUnit