SlideShare a Scribd company logo
Testing untestable code
   Stephan Hochdörfer, bitExpert AG




 "Quality is a function of thought and reflection -
 precise thought and reflection. That’s the magic."
                 Michael Feathers
About me
 Stephan Hochdörfer, bitExpert AG

 Department Manager Research Labs

 enjoying PHP since 1999

 S.Hochdoerfer@bitExpert.de

 @shochdoerfer
Warning! Use at your own risk...
No excuse for writing bad code!
Seriously, I am not kidding!
Theory




         "There is no secret to writing tests, there
         are only secrets to write testable code!"
                         Miško Hevery
Theory


 What is „untestable code“?
Theory


 What is „untestable code“?




                          s




         „new“ is evil!
Theory


 What is „untestable code“?
Theory


 What is „untestable code“?
Theory




          "...our test strategy requires us to have more control or
         visibility of the internal behavior of the system under test."
               Gerard Meszaros, xUnit Test Patterns: Refactoring Test Code
Theory




                                 Required
                                  Required
                                  class
                                   class
                     Class to
         Unittest     Class to
          Unittest     Test
                        Test
                                 Required
                                  Required
                                  class
                                   class
Theory




                                             Database
                                              Database
                                 Required
                                  Required
                                  class
                                   class
                                             External
                     Class to                  External
         Unittest     Class to               resource
          Unittest     test                   resource
                         test
                                 Required
                                  Required
                                  class
                                   class




                     Required    Required
                      Required    Required   Webservice
                      class       class       Webservice
                       class       class
Theory




                                             Database
                                              Database
                                 Required
                                  Required
                                  class
                                   class
                                             External
                     Class to                  External
         Unittest     Class to               resource
          Unittest     test                   resource
                         test
                                 Required
                                  Required
                                  class
                                   class




                     Required    Required
                      Required    Required   Webservice
                      class       class       Webservice
                       class       class
Theory


 How to achieve „testable“ code?
Theory


 How to achieve „testable“ code?




                Refactoring
Theory




         "Before you start refactoring, check that you
                  have a solid suite of tests."
                     Martin Fowler, Refactoring
Testing „untestable“ PHP Code


 Let the work begin...
Testing „untestable“ PHP Code


 Safty instructions




          Do not change existing code!
Testing „untestable“ PHP Code | __autoload




<?php
class Car {
    private $Engine;

     public function __construct($sEngine) {
         $this->Engine = Engine::getByType($sEngine);
     }

}
Testing „untestable“ PHP Code | __autoload




<?php
class Car {
    private $Engine;

     public function __construct($sEngine) {
         $this->Engine = Engine::getByType($sEngine);
     }

}




How to inject a dependency?
 Use __autoload
Testing „untestable“ PHP Code | __autoload




<?php
function run_autoload($psClass) {
    $sFileToInclude = strtolower($psClass).'.php';
    if(strtolower($psClass) == 'engine') {
         $sFileToInclude = '/custom/mocks/'.$sFileToInclude;
    }
    include($sFileToInclude);
}


// Testcase
spl_autoload_register('run_autoload');
$oCar = new Car('Diesel');
echo $oCar->run();
Testing „untestable“ PHP Code | include_path




<?php
include('Engine.php');

class Car {
    private $Engine;

     public function __construct($sEngine) {
         $this->Engine = Engine::getByType($sEngine);
     }
}
Testing „untestable“ PHP Code | include_path




<?php
include('Engine.php');

class Car {
    private $Engine;

     public function __construct($sEngine) {
         $this->Engine = Engine::getByType($sEngine);
     }
}




How to inject a dependency?
 Manipulate include_path setting
Testing „untestable“ PHP Code | include_path




<?php
ini_set('include_path',
    '/custom/mocks/'.PATH_SEPARATOR.
    ini_get('include_path'));

// Testcase
include('car.php');

$oCar = new Car('Diesel');
echo $oCar->run();
Testing „untestable“ PHP Code | include_path alternative




<?php
include('Engine.php');

class Car {
    private $Engine;

     public function __construct($sEngine) {
         $this->Engine = Engine::getByType($sEngine);
     }
}
Testing „untestable“ PHP Code | include_path alternative




<?php
include('Engine.php');

class Car {
    private $Engine;

     public function __construct($sEngine) {
         $this->Engine = Engine::getByType($sEngine);
     }
}




How to inject a dependency?
 Custom Stream Wrapper behaviour

 Idea by Alex Netkachov, http://www.alexatnet.com/node/203
Testing „untestable“ PHP Code | include_path alternative

<?php
class CustomFileStreamWrapper {
  private $_handler;

    function stream_open($path, $mode, $options, &$opened_path) {
      stream_wrapper_restore('file');
      // @TODO: modify $path before fopen
      $this->_handler = fopen($path, $mode);
      stream_wrapper_unregister('file');
      stream_wrapper_register('file', 'CustomFileStreamWrapper');
      return true;
    }

    function stream_read($count) {}

    function stream_write($data) {}

    function stream_tell() {}

    function stream_eof() {}

    function stream_seek($offset, $whence) {}
}

stream_wrapper_unregister('file');
stream_wrapper_register('file', 'CustomFileStreamWrapper');
Testing „untestable“ PHP Code | include_path alternative

<?php
class CustomFileStreamWrapper {
    private $_handler;

     function stream_open($path, $mode, $options, &$opened_path) {
         stream_wrapper_restore('file');
         $this->_handler = fopen($path, $mode);
         stream_wrapper_unregister('file');
         stream_wrapper_register('file', 'CustomFileStreamWrapper');
         return true;
     }

     function stream_read($count) {
         $content = fread($this->_handler, $count);
         $content = str_replace('Engine::getByType', 'AbstractEngine::get',
              $content);
         return $content;
     }
}

stream_wrapper_unregister('file');
stream_wrapper_register('file', 'CustomFileStreamWrapper');

include('engine.php');
?>
Testing „untestable“ PHP Code


 How to test private methods?
Testing „untestable“ PHP Code


 How to test private methods?




                     There`s no need to!
Testing „untestable“ PHP Code


 How to test private methods?




              There`s no need to, but...
Testing „untestable“ PHP Code | private vs. protected

<?php
class CustomFileStreamWrapper {
    private $_handler;

     function stream_open($path, $mode, $options, &$opened_path) {
         stream_wrapper_restore('file');
         $this->_handler = fopen($path, $mode);
         stream_wrapper_unregister('file');
         stream_wrapper_register('file', 'CustomFileStreamWrapper');
         return true;
     }

     function stream_read($count) {
         $content = fread($this->_handler, $count);
         $content = str_replace('private function', 'public function',
              $content);
         return $content;
     }
}

stream_wrapper_unregister('file');
stream_wrapper_register('file', 'CustomFileStreamWrapper');

include('engine.php');
?>
Testing „untestable“ PHP Code | Namespaces




<?php
class Car {
    private $Engine;

     public function __construct($sEngine) {
         $this->Engine = CarEngine::getByType($sEngine);
     }
}
Testing „untestable“ PHP Code | Namespaces




<?php
class Car {
    private $Engine;

     public function __construct($sEngine) {
         $this->Engine = CarEngine::getByType($sEngine);
     }
}




How to inject a dependency?
 Use __autoload or manipulate the include_path
Testing „untestable“ PHP Code | vfsStream




<?php
class Car {
    private $Engine;

     public function __construct($sEngine, $CacheDir) {
         $this->Engine = CarEngine::getByType($sEngine);

          mkdir($CacheDir.'/cache/', 0700, true);
     }
}
Testing „untestable“ PHP Code | vfsStream




<?php
class Car {
    private $Engine;

     public function __construct($sEngine, $CacheDir) {
         $this->Engine = CarEngine::getByType($sEngine);

          mkdir($CacheDir.'/cache/', 0700, true);
     }
}




How mock a filesystem?
 Use vfsStream - http://code.google.com/p/bovigo/
Testing „untestable“ PHP Code | vfsStream




<?php

// setup vfsStream
vfsStreamWrapper::register();
vfsStreamWrapper::setRoot(new vfsStreamDirectory('app'));

$oCar = new Car('Diesel', vfsStream::url('app'));

echo vfsStreamWrapper::getRoot()->hasChild('cache');
Testing „untestable“ PHP Code | Database



 Database Testing

  Use the methods provided by your favourite framework

  e.g Zend Framework

              Implement Zend_Db_Statement_Interface

              subclass Zend_Db_Adapter_Abstract

               $db = new Custom_Db_Adapter(array());
               Zend_Db_Table::setDefaultAdapter($db);
Testing „untestable“ PHP Code | Database



 Database Testing

  For low-level database access:

  e.g MySQL

              do not load the mysql extension

              Add custom userland implementations of mysql_* functions
Testing „untestable“ PHP Code | Database



 Database Testing

  Use the methods provided by your favourite unittest framework

  e.g PHPUnit

              extend PHPUnit_Extensions_Database_TestCase

              implement getConnection() and getDataset()
Testing „untestable“ PHP Code | Database



 Database Testing

  Use the methods provided by your favourite sql server (and tools)

  e.g MySQL

              use MySQL Proxy to transparently switch databases

              Begin and rollback transactions
Testing „untestable“ PHP Code




       „I have no idea how to unit-test procedural code. Unit-testing
          assumes that I can instantiate a piece of my application in
                                  isolation.“
                                Miško Hevery
Testing „untestable“ PHP Code | Test functions




<?php
function startsWith($sString, $psPre) {
    return $psPre == substr($sString, 0, strlen($psPre));
}

function contains($sString, $sSearch) {
    return false !== strpos($sString, $sSearch);
}
Testing „untestable“ PHP Code | Test functions




<?php
function startsWith($sString, $psPre) {
    return $psPre == substr($sString, 0, strlen($psPre));
}

function contains($sString, $sSearch) {
    return false !== strpos($sString, $sSearch);
}




How to test
 PHPUnit can call functions
Testing „untestable“ PHP Code | Test functions




<?php
function startsWith($sString, $psPre) {
    return $psPre == substr($sString, 0, strlen($psPre));
}

function contains($sString, $sSearch) {
    return false !== strpos($sString, $sSearch);
}




How to test
 PHPUnit can call functions

 PHPUnit can save/restore globale state
Testing „untestable“ PHP Code | overwrite internal functions




<?php
function buyCar(Car $oCar) {
    global $oDB;

     mysql_query("INSERT INTO...", $oDB);

     mail('order@domain.org', 'New sale', '....');
}
Testing „untestable“ PHP Code | overwrite internal functions




<?php
function buyCar(Car $oCar) {
    global $oDB;

     mysql_query("INSERT INTO...", $oDB);

     mail('order@domain.org', 'New sale', '....');
}
Testing „untestable“ PHP Code | overwrite internal functions




<?php
function buyCar(Car $oCar) {
    global $oDB;

     mysql_query("INSERT INTO...", $oDB);

     mail('order@domain.org', 'New sale', '....');
}




How to test
 Unfortunatley mail() is part of the PHP core and cannot be unloaded
Testing „untestable“ PHP Code | overwrite internal functions




<?php
function buyCar(Car $oCar) {
    global $oDB;

     mysql_query("INSERT INTO...", $oDB);

     mail('order@domain.org', 'New sale', '....');
}




How to test
 Use classkit extension to overwrite internal functions
Testing „untestable“ PHP Code | overwrite internal functions




<?php

ini_set('runkit.internal_override', '1');

runkit_function_redefine('mail','','return true;');

?>
Testing „untestable“ PHP Code
Generating testable code


 What else?




               Remember:
       No changes to the source code!
Generating testable code


 What else?




               Generative Programming
Generating testable code


 Generative Programming

                           Configuration
                            Configuration




                                                          1 ... n
            Implementation
             Implementation                  Generator     Product
              components                      Generator     Product
               components




                              Generator
                               Generator
                              application
                               application
Generating testable code


 Generative Programming

                           Configuration
                            Configuration

                                                          Application
                                                           Application


            Implementation
             Implementation                  Generator
              components                      Generator
               components



                                                          Testcases
                                                           Testcases
                              Generator
                               Generator
                              application
                               application
Generating testable code


 Generative Programming




            A frame is a data structure
           for representing knowledge.
Generating testable code




FileFrm FILEIndex_php5 {
  private String Prefix   = "test_";
  private String MailSlot = "mail('order@domain.org', 'New sale', '....');";

   public FILEIndex_php5() {
     setFilename("index.php5");
     setRelativePath("/");
   }

  private void assign() {
BEGINCONTENT()
<?php
function buyCar(Car $oCar) {
global $oDB;

<!{Prefix}!>mysql_query(„INSERT INTO...“, $oDB);
<!{MailSlot}!>
}

?>
ENDCONTENT()
  }
}
Generating testable code




FileFrm FILEIndex_php5 {
  private String Prefix   = "test_";
  private String MailSlot = "mail('order@domain.org', 'New sale', '....');";

   public FILEIndex_php5() {
     setFilename("index.php5");
     setRelativePath("/");
   }

  private void assign() {
BEGINCONTENT()
<?php
function buyCar(Car $oCar) {
global $oDB;

<!{Prefix}!>mysql_query(„INSERT INTO...“, $oDB);
<!{MailSlot}!>
}

?>
ENDCONTENT()
   }
}
Generating testable code


 Generative Programming
 Extraction
  Show / hide parts of the code



 Example
 MailSlot: mail('order@domain.org', 'New sale', '....');
Generating testable code


 Generative Programming
 Extraction
  Show / hide parts of the code



 Example
 MailSlot: mail('order@domain.org', 'New sale', '....');

 <?php
 function buyCar(Car $oCar) {
   global $oDB;

      mysql_query("INSERT INTO...", $oDB);
      mail('order@domain.org', 'New sale', '....');
 }

 ?>
Generating testable code


 Course of action
 Customizing
  Change content of global vars
  Pre/Postfixes for own functions, methods, classes

 Example
 Prefix: test_
Generating testable code


 Course of action
 Customizing
  Change content of global vars
  Pre/Postfixes for own functions, methods, classes

 Example
 Prefix: test_

 <?php
 function buyCar(Car $oCar) {
   global $oDB;

     test_mysql_query("INSERT INTO...", $oDB);
 }
Conclusion


 How much effort to take?
Conclusion



 Conclusion




             Change your mindset to write
                    testable code!
Conclusion



 Conclusion




             PHP is a swiss army knife.
http://joind.in/2420

More Related Content

PDF
Testing untestable code - phpconpl11
PDF
Testing untestable code - DPC10
PDF
Real World Dependency Injection - phpday
PDF
Testing untestable code - STPCon11
PPT
Invoke dynamics
PPTX
Playing with Java Classes and Bytecode
PDF
Advanced java interview questions
ODP
Method Handles in Java
Testing untestable code - phpconpl11
Testing untestable code - DPC10
Real World Dependency Injection - phpday
Testing untestable code - STPCon11
Invoke dynamics
Playing with Java Classes and Bytecode
Advanced java interview questions
Method Handles in Java

What's hot (20)

DOCX
Viva file
PDF
Java Faqs useful for freshers and experienced
PPTX
Java reflection
PDF
Living With Legacy Code
PDF
"Formal Verification in Java" by Shura Iline, Vladimir Ivanov @ JEEConf 2013,...
PDF
Java Annotation Processing: A Beginner Walkthrough
PDF
Getting started with the JNI
PPTX
Dev labs alliance top 20 basic java interview question for sdet
PPTX
Java Reflection @KonaTechAdda
PDF
Serial Killer - Silently Pwning your Java Endpoints // OWASP BeNeLux Day 2016
PDF
Java SE 8 best practices
ODP
Mastering Mock Objects - Advanced Unit Testing for Java
PPT
Java Tutorial
PDF
Cracking OCA and OCP Java 8 Exams
PDF
Working Effectively With Legacy Perl Code
PPTX
OCP Java (OCPJP) 8 Exam Quick Reference Card
PDF
Java object oriented programming - OOPS
PDF
Exception handling
PPT
Java tutorials
PDF
02 basic java programming and operators
Viva file
Java Faqs useful for freshers and experienced
Java reflection
Living With Legacy Code
"Formal Verification in Java" by Shura Iline, Vladimir Ivanov @ JEEConf 2013,...
Java Annotation Processing: A Beginner Walkthrough
Getting started with the JNI
Dev labs alliance top 20 basic java interview question for sdet
Java Reflection @KonaTechAdda
Serial Killer - Silently Pwning your Java Endpoints // OWASP BeNeLux Day 2016
Java SE 8 best practices
Mastering Mock Objects - Advanced Unit Testing for Java
Java Tutorial
Cracking OCA and OCP Java 8 Exams
Working Effectively With Legacy Perl Code
OCP Java (OCPJP) 8 Exam Quick Reference Card
Java object oriented programming - OOPS
Exception handling
Java tutorials
02 basic java programming and operators
Ad

Similar to Testing untestable code - PHPBNL11 (20)

PDF
Testing untestable Code - PFCongres 2010
KEY
Developer testing 101: Become a Testing Fanatic
PDF
Leveling Up With Unit Testing - LonghornPHP 2022
PDF
Real world dependency injection - DPC10
PDF
What's new in PHP 8.0?
PDF
Nikita Popov "What’s new in PHP 8.0?"
PDF
Testing untestable code - phpday
PDF
Leveling Up With Unit Testing - php[tek] 2023
PDF
PCAP Certification Mini Flashcards by MyExamCloud
PPTX
Test in action week 2
PDF
Real World Dependency Injection - PFCongres 2010
PPT
PHP - Introduction to Object Oriented Programming with PHP
ZIP
Test
PPTX
Test in action – week 1
PDF
Invoke dynamite in Java EE with invoke dynamic
KEY
Developer testing 201: When to Mock and When to Integrate
PDF
Unit Testing from Setup to Deployment
PDF
Better Testing With PHP Unit
PPTX
Framework prototype
PPTX
Framework prototype
Testing untestable Code - PFCongres 2010
Developer testing 101: Become a Testing Fanatic
Leveling Up With Unit Testing - LonghornPHP 2022
Real world dependency injection - DPC10
What's new in PHP 8.0?
Nikita Popov "What’s new in PHP 8.0?"
Testing untestable code - phpday
Leveling Up With Unit Testing - php[tek] 2023
PCAP Certification Mini Flashcards by MyExamCloud
Test in action week 2
Real World Dependency Injection - PFCongres 2010
PHP - Introduction to Object Oriented Programming with PHP
Test
Test in action – week 1
Invoke dynamite in Java EE with invoke dynamic
Developer testing 201: When to Mock and When to Integrate
Unit Testing from Setup to Deployment
Better Testing With PHP Unit
Framework prototype
Framework prototype
Ad

More from Stephan Hochdörfer (20)

PDF
Offline. Na und? Strategien für offlinefähige Applikationen in HTML5 - Herbst...
PDF
Phing for power users - frOSCon8
PDF
Offline strategies for HTML5 web applications - frOSCon8
PDF
Offline Strategies for HTML5 Web Applications - oscon13
PDF
Real World Dependency Injection - oscon13
PDF
Dependency Injection in PHP - dwx13
PDF
Offline Strategien für HTML5 Web Applikationen - dwx13
PDF
Your Business. Your Language. Your Code - dpc13
PDF
Phing for power users - dpc_uncon13
PDF
Offline Strategies for HTML5 Web Applications - ipc13
PDF
Offline-Strategien für HTML5 Web Applikationen - wmka
PDF
Offline-Strategien für HTML5 Web Applikationen - bedcon13
PDF
Real World Dependency Injection - phpugffm13
PDF
Testing untestable code - ConFoo13
PDF
A Phing fairy tale - ConFoo13
PDF
Offline strategies for HTML5 web applications - ConFoo13
PDF
Offline-Strategien für HTML5Web Applikationen - WMMRN12
PDF
Testing untestable code - IPC12
PDF
Offline strategies for HTML5 web applications - IPC12
PDF
Große Systeme, lose Kopplung, Spaß bei der Arbeit! - WDC12
Offline. Na und? Strategien für offlinefähige Applikationen in HTML5 - Herbst...
Phing for power users - frOSCon8
Offline strategies for HTML5 web applications - frOSCon8
Offline Strategies for HTML5 Web Applications - oscon13
Real World Dependency Injection - oscon13
Dependency Injection in PHP - dwx13
Offline Strategien für HTML5 Web Applikationen - dwx13
Your Business. Your Language. Your Code - dpc13
Phing for power users - dpc_uncon13
Offline Strategies for HTML5 Web Applications - ipc13
Offline-Strategien für HTML5 Web Applikationen - wmka
Offline-Strategien für HTML5 Web Applikationen - bedcon13
Real World Dependency Injection - phpugffm13
Testing untestable code - ConFoo13
A Phing fairy tale - ConFoo13
Offline strategies for HTML5 web applications - ConFoo13
Offline-Strategien für HTML5Web Applikationen - WMMRN12
Testing untestable code - IPC12
Offline strategies for HTML5 web applications - IPC12
Große Systeme, lose Kopplung, Spaß bei der Arbeit! - WDC12

Testing untestable code - PHPBNL11

  • 1. Testing untestable code Stephan Hochdörfer, bitExpert AG "Quality is a function of thought and reflection - precise thought and reflection. That’s the magic." Michael Feathers
  • 2. About me  Stephan Hochdörfer, bitExpert AG  Department Manager Research Labs  enjoying PHP since 1999  S.Hochdoerfer@bitExpert.de  @shochdoerfer
  • 3. Warning! Use at your own risk...
  • 4. No excuse for writing bad code!
  • 5. Seriously, I am not kidding!
  • 6. Theory "There is no secret to writing tests, there are only secrets to write testable code!" Miško Hevery
  • 7. Theory What is „untestable code“?
  • 8. Theory What is „untestable code“? s „new“ is evil!
  • 9. Theory What is „untestable code“?
  • 10. Theory What is „untestable code“?
  • 11. Theory "...our test strategy requires us to have more control or visibility of the internal behavior of the system under test." Gerard Meszaros, xUnit Test Patterns: Refactoring Test Code
  • 12. Theory Required Required class class Class to Unittest Class to Unittest Test Test Required Required class class
  • 13. Theory Database Database Required Required class class External Class to External Unittest Class to resource Unittest test resource test Required Required class class Required Required Required Required Webservice class class Webservice class class
  • 14. Theory Database Database Required Required class class External Class to External Unittest Class to resource Unittest test resource test Required Required class class Required Required Required Required Webservice class class Webservice class class
  • 15. Theory How to achieve „testable“ code?
  • 16. Theory How to achieve „testable“ code? Refactoring
  • 17. Theory "Before you start refactoring, check that you have a solid suite of tests." Martin Fowler, Refactoring
  • 18. Testing „untestable“ PHP Code Let the work begin...
  • 19. Testing „untestable“ PHP Code Safty instructions Do not change existing code!
  • 20. Testing „untestable“ PHP Code | __autoload <?php class Car { private $Engine; public function __construct($sEngine) { $this->Engine = Engine::getByType($sEngine); } }
  • 21. Testing „untestable“ PHP Code | __autoload <?php class Car { private $Engine; public function __construct($sEngine) { $this->Engine = Engine::getByType($sEngine); } } How to inject a dependency?  Use __autoload
  • 22. Testing „untestable“ PHP Code | __autoload <?php function run_autoload($psClass) { $sFileToInclude = strtolower($psClass).'.php'; if(strtolower($psClass) == 'engine') { $sFileToInclude = '/custom/mocks/'.$sFileToInclude; } include($sFileToInclude); } // Testcase spl_autoload_register('run_autoload'); $oCar = new Car('Diesel'); echo $oCar->run();
  • 23. Testing „untestable“ PHP Code | include_path <?php include('Engine.php'); class Car { private $Engine; public function __construct($sEngine) { $this->Engine = Engine::getByType($sEngine); } }
  • 24. Testing „untestable“ PHP Code | include_path <?php include('Engine.php'); class Car { private $Engine; public function __construct($sEngine) { $this->Engine = Engine::getByType($sEngine); } } How to inject a dependency?  Manipulate include_path setting
  • 25. Testing „untestable“ PHP Code | include_path <?php ini_set('include_path', '/custom/mocks/'.PATH_SEPARATOR. ini_get('include_path')); // Testcase include('car.php'); $oCar = new Car('Diesel'); echo $oCar->run();
  • 26. Testing „untestable“ PHP Code | include_path alternative <?php include('Engine.php'); class Car { private $Engine; public function __construct($sEngine) { $this->Engine = Engine::getByType($sEngine); } }
  • 27. Testing „untestable“ PHP Code | include_path alternative <?php include('Engine.php'); class Car { private $Engine; public function __construct($sEngine) { $this->Engine = Engine::getByType($sEngine); } } How to inject a dependency?  Custom Stream Wrapper behaviour  Idea by Alex Netkachov, http://www.alexatnet.com/node/203
  • 28. Testing „untestable“ PHP Code | include_path alternative <?php class CustomFileStreamWrapper { private $_handler; function stream_open($path, $mode, $options, &$opened_path) { stream_wrapper_restore('file'); // @TODO: modify $path before fopen $this->_handler = fopen($path, $mode); stream_wrapper_unregister('file'); stream_wrapper_register('file', 'CustomFileStreamWrapper'); return true; } function stream_read($count) {} function stream_write($data) {} function stream_tell() {} function stream_eof() {} function stream_seek($offset, $whence) {} } stream_wrapper_unregister('file'); stream_wrapper_register('file', 'CustomFileStreamWrapper');
  • 29. Testing „untestable“ PHP Code | include_path alternative <?php class CustomFileStreamWrapper { private $_handler; function stream_open($path, $mode, $options, &$opened_path) { stream_wrapper_restore('file'); $this->_handler = fopen($path, $mode); stream_wrapper_unregister('file'); stream_wrapper_register('file', 'CustomFileStreamWrapper'); return true; } function stream_read($count) { $content = fread($this->_handler, $count); $content = str_replace('Engine::getByType', 'AbstractEngine::get', $content); return $content; } } stream_wrapper_unregister('file'); stream_wrapper_register('file', 'CustomFileStreamWrapper'); include('engine.php'); ?>
  • 30. Testing „untestable“ PHP Code How to test private methods?
  • 31. Testing „untestable“ PHP Code How to test private methods? There`s no need to!
  • 32. Testing „untestable“ PHP Code How to test private methods? There`s no need to, but...
  • 33. Testing „untestable“ PHP Code | private vs. protected <?php class CustomFileStreamWrapper { private $_handler; function stream_open($path, $mode, $options, &$opened_path) { stream_wrapper_restore('file'); $this->_handler = fopen($path, $mode); stream_wrapper_unregister('file'); stream_wrapper_register('file', 'CustomFileStreamWrapper'); return true; } function stream_read($count) { $content = fread($this->_handler, $count); $content = str_replace('private function', 'public function', $content); return $content; } } stream_wrapper_unregister('file'); stream_wrapper_register('file', 'CustomFileStreamWrapper'); include('engine.php'); ?>
  • 34. Testing „untestable“ PHP Code | Namespaces <?php class Car { private $Engine; public function __construct($sEngine) { $this->Engine = CarEngine::getByType($sEngine); } }
  • 35. Testing „untestable“ PHP Code | Namespaces <?php class Car { private $Engine; public function __construct($sEngine) { $this->Engine = CarEngine::getByType($sEngine); } } How to inject a dependency?  Use __autoload or manipulate the include_path
  • 36. Testing „untestable“ PHP Code | vfsStream <?php class Car { private $Engine; public function __construct($sEngine, $CacheDir) { $this->Engine = CarEngine::getByType($sEngine); mkdir($CacheDir.'/cache/', 0700, true); } }
  • 37. Testing „untestable“ PHP Code | vfsStream <?php class Car { private $Engine; public function __construct($sEngine, $CacheDir) { $this->Engine = CarEngine::getByType($sEngine); mkdir($CacheDir.'/cache/', 0700, true); } } How mock a filesystem?  Use vfsStream - http://code.google.com/p/bovigo/
  • 38. Testing „untestable“ PHP Code | vfsStream <?php // setup vfsStream vfsStreamWrapper::register(); vfsStreamWrapper::setRoot(new vfsStreamDirectory('app')); $oCar = new Car('Diesel', vfsStream::url('app')); echo vfsStreamWrapper::getRoot()->hasChild('cache');
  • 39. Testing „untestable“ PHP Code | Database Database Testing  Use the methods provided by your favourite framework  e.g Zend Framework  Implement Zend_Db_Statement_Interface  subclass Zend_Db_Adapter_Abstract $db = new Custom_Db_Adapter(array()); Zend_Db_Table::setDefaultAdapter($db);
  • 40. Testing „untestable“ PHP Code | Database Database Testing  For low-level database access:  e.g MySQL  do not load the mysql extension  Add custom userland implementations of mysql_* functions
  • 41. Testing „untestable“ PHP Code | Database Database Testing  Use the methods provided by your favourite unittest framework  e.g PHPUnit  extend PHPUnit_Extensions_Database_TestCase  implement getConnection() and getDataset()
  • 42. Testing „untestable“ PHP Code | Database Database Testing  Use the methods provided by your favourite sql server (and tools)  e.g MySQL  use MySQL Proxy to transparently switch databases  Begin and rollback transactions
  • 43. Testing „untestable“ PHP Code „I have no idea how to unit-test procedural code. Unit-testing assumes that I can instantiate a piece of my application in isolation.“ Miško Hevery
  • 44. Testing „untestable“ PHP Code | Test functions <?php function startsWith($sString, $psPre) { return $psPre == substr($sString, 0, strlen($psPre)); } function contains($sString, $sSearch) { return false !== strpos($sString, $sSearch); }
  • 45. Testing „untestable“ PHP Code | Test functions <?php function startsWith($sString, $psPre) { return $psPre == substr($sString, 0, strlen($psPre)); } function contains($sString, $sSearch) { return false !== strpos($sString, $sSearch); } How to test  PHPUnit can call functions
  • 46. Testing „untestable“ PHP Code | Test functions <?php function startsWith($sString, $psPre) { return $psPre == substr($sString, 0, strlen($psPre)); } function contains($sString, $sSearch) { return false !== strpos($sString, $sSearch); } How to test  PHPUnit can call functions  PHPUnit can save/restore globale state
  • 47. Testing „untestable“ PHP Code | overwrite internal functions <?php function buyCar(Car $oCar) { global $oDB; mysql_query("INSERT INTO...", $oDB); mail('order@domain.org', 'New sale', '....'); }
  • 48. Testing „untestable“ PHP Code | overwrite internal functions <?php function buyCar(Car $oCar) { global $oDB; mysql_query("INSERT INTO...", $oDB); mail('order@domain.org', 'New sale', '....'); }
  • 49. Testing „untestable“ PHP Code | overwrite internal functions <?php function buyCar(Car $oCar) { global $oDB; mysql_query("INSERT INTO...", $oDB); mail('order@domain.org', 'New sale', '....'); } How to test  Unfortunatley mail() is part of the PHP core and cannot be unloaded
  • 50. Testing „untestable“ PHP Code | overwrite internal functions <?php function buyCar(Car $oCar) { global $oDB; mysql_query("INSERT INTO...", $oDB); mail('order@domain.org', 'New sale', '....'); } How to test  Use classkit extension to overwrite internal functions
  • 51. Testing „untestable“ PHP Code | overwrite internal functions <?php ini_set('runkit.internal_override', '1'); runkit_function_redefine('mail','','return true;'); ?>
  • 53. Generating testable code What else? Remember: No changes to the source code!
  • 54. Generating testable code What else? Generative Programming
  • 55. Generating testable code Generative Programming Configuration Configuration 1 ... n Implementation Implementation Generator Product components Generator Product components Generator Generator application application
  • 56. Generating testable code Generative Programming Configuration Configuration Application Application Implementation Implementation Generator components Generator components Testcases Testcases Generator Generator application application
  • 57. Generating testable code Generative Programming A frame is a data structure for representing knowledge.
  • 58. Generating testable code FileFrm FILEIndex_php5 { private String Prefix = "test_"; private String MailSlot = "mail('order@domain.org', 'New sale', '....');"; public FILEIndex_php5() { setFilename("index.php5"); setRelativePath("/"); } private void assign() { BEGINCONTENT() <?php function buyCar(Car $oCar) { global $oDB; <!{Prefix}!>mysql_query(„INSERT INTO...“, $oDB); <!{MailSlot}!> } ?> ENDCONTENT() } }
  • 59. Generating testable code FileFrm FILEIndex_php5 { private String Prefix = "test_"; private String MailSlot = "mail('order@domain.org', 'New sale', '....');"; public FILEIndex_php5() { setFilename("index.php5"); setRelativePath("/"); } private void assign() { BEGINCONTENT() <?php function buyCar(Car $oCar) { global $oDB; <!{Prefix}!>mysql_query(„INSERT INTO...“, $oDB); <!{MailSlot}!> } ?> ENDCONTENT() } }
  • 60. Generating testable code Generative Programming Extraction  Show / hide parts of the code Example MailSlot: mail('order@domain.org', 'New sale', '....');
  • 61. Generating testable code Generative Programming Extraction  Show / hide parts of the code Example MailSlot: mail('order@domain.org', 'New sale', '....'); <?php function buyCar(Car $oCar) { global $oDB; mysql_query("INSERT INTO...", $oDB); mail('order@domain.org', 'New sale', '....'); } ?>
  • 62. Generating testable code Course of action Customizing  Change content of global vars  Pre/Postfixes for own functions, methods, classes Example Prefix: test_
  • 63. Generating testable code Course of action Customizing  Change content of global vars  Pre/Postfixes for own functions, methods, classes Example Prefix: test_ <?php function buyCar(Car $oCar) { global $oDB; test_mysql_query("INSERT INTO...", $oDB); }
  • 64. Conclusion How much effort to take?
  • 65. Conclusion Conclusion Change your mindset to write testable code!
  • 66. Conclusion Conclusion PHP is a swiss army knife.