Testing untestable code Stephan Hochdörfer, bitExpert AG
Testing untestable code About me  Stephan Hochdörfer  Head of IT at bitExpert AG, Germany  enjoying PHP since 1999  S.Hochdoerfer@bitExpert.de  @shochdoerfer
Testing untestable code No excuse for writing bad code!
Testing untestable code "Hang the rules. They're more like guidelines anyway." Elizabeth Swann, Pirates of the Caribbean
Testing untestable code "There is no secret to writing tests, there are only secrets to write testable code!" Miško Hevery
Testing untestable code What is „untestable code“?
Testing untestable code "...our test strategy requires us to have more control [...] of the sut." Gerard Meszaros, xUnit Test Patterns: Refactoring Test Code
Testing untestable code In a perfect world... Unittest Unittest SUT SUT
Testing untestable code Legacy code is not perfect... Dependency Dependency Unittest Unittest SUT SUT Dependency Dependency
Testing untestable code ... Legacy code is not perfect... Dependency Dependency Unittest Unittest SUT SUT Dependency Dependency ...
Testing untestable code ... Legacy code is not perfect... Dependency Dependency Unittest Unittest SUT SUT Dependency Dependency ...
Testing untestable code How to get „testable“ code?
Testing untestable code How to get „testable“ code? Refactoring
Testing untestable code "Before you start refactoring, check that you have a solid suite of tests." Martin Fowler, Refactoring
Testing untestable code Which path to take?
Testing untestable code Which path to take? Do not change existing code!
Testing untestable code Examples Object Construction External resources Language issues
Testing untestable code Object construction <?php class Car { private $Engine; public function __construct($sEngine) { $this­>Engine = Engine::getByType($sEngine); } }
Testing untestable code Object construction - 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 code Object construction <?php include('Engine.php'); class Car { private $Engine; public function __construct($sEngine) { $this­>Engine = Engine::getByType($sEngine); } }
Testing untestable code Object construction - 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 code Object construction – Stream Wrapper <?php class CustomWrapper {   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', 'CustomWrapper');     return true;   } }
Testing untestable code Object construction – Stream Wrapper stream_wrapper_unregister('file'); stream_wrapper_register('file', 'CustomWrapper');
Testing untestable code Object construction – Stream Wrapper <?php class CustomWrapper { private $_handler; function stream_read($count) { $content = fread($this­>_handler, $count); $content = str_replace('Engine::getByType',        'AbstractEngine::get', $content); return $content; } }
Testing untestable code External resources
Testing untestable code External resources Database Webservice Filesystem Mailserver
Testing untestable code External resources – Mock database
Testing untestable code External resources – Mock database Provide own implementation
Testing untestable code External resources – Mock database ZF1 example: $db = new Custom_Db_Adapter(array()); Zend_Db_Table::setDefaultAdapter($db);
Testing untestable code External resources – Mock database PHPUnit_Extensions_Database_TestCase
Testing untestable code External resources – Mock database require_once  "PHPUnit/Extensions/Database/TestCase.php"; class MySampleTest extends  PHPUnit_Extensions_Database_TestCase {     public function getConnection() {         $pdo = new PDO('sqlite::memory:');         return $this­>createDefaultDBConnection         $pdo, ':memory:');     }     public function getDataSet() {         return $this­>createFlatXMLDataSet(         dirname(__FILE__).'/_files/data.xml'     );     } }
Testing untestable code External resources – Mock database Proxy for your SQL Server
Testing untestable code External resources – Mock webservice
Testing untestable code External resources – Mock webservice Provide own implementation
Testing untestable code External resources – Mock webservice Host redirect via /etc/hosts
Testing untestable code External resources – Mock filesystem
Testing untestable code External resources – Mock filesystem <?php // set up test environmemt vfsStream::setup('exampleDir'); // create directory in test enviroment mkdir(vfsStream::url('exampleDir').'/sample/'); // check if directory was created echo vfsStreamWrapper::getRoot()­>hasChild('sample');
Testing untestable code External resources – Mock Mailserver
Testing untestable code External resources – Mock Mailserver Use fake mail server
Testing untestable code External resources – Mock Mailserver $ cat /etc/php5/php.ini | grep sendmail_path sendmail_path=/usr/local/bin/logmail $ cat /usr/local/bin/logmail cat >> /tmp/logmail.log
Testing untestable code Dealing with language issues
Testing untestable code Dealing with language issues Testing your privates?
Testing untestable code Dealing with language issues <?php class CustomWrapper { private $_handler; function stream_read($count) { $content = fread($this­>_handler, $count); $content = str_replace(          'private function',          'public function',           $content       ); return $content; } }
Testing untestable code Dealing with language issues $myClass = new MyClass(); $reflectionClass  = new ReflectionClass('MyClass'); $reflectionMethod = $reflectionClass­> getMethod('mydemo'); $reflectionMethod­>setAccessible(true); $reflectionMethod­>invoke($myClass);
Testing untestable code Dealing with language issues Overwrite internal functions?
Testing untestable code Dealing with language issues pecl install runkit-0.9
Testing untestable code Dealing with language issues - Runkit <?php ini_set('runkit.internal_override', '1'); runkit_function_redefine('mail','','return  true;'); ?>
Testing untestable code Dealing with language issues pecl install funcall-0.3.0alpha
Testing untestable code Dealing with language issues - Funcall <?php function my_func($arg1, $arg2) {     return $arg1.$arg2; } function post_cb($args,$result, $process_time) {   // return custom result based on  $args } fc_add_post('my_func','post_cb'); var_dump(my_func('php', 'c'));
Testing untestable code Dealing with language issues funcall for methods?
Testing untestable code Dealing with language issues git clone https://github/juliens/AOP
Testing untestable code Dealing with language issues - AOP <?php aop_add_after('Car::drive*',  'adviceForDrive');
Testing untestable code Dealing with language issues - AOP <?php $advice = function(AopTriggeredJoinpoint $jp) {   $returnValue =       $jp­>getReturnedValue();   // modify the return value   $returnValue = 1234;   $jp­>setReturnedValue($returnValue); }; aop_add_after('Car­>drive()', $advice);
Testing untestable code And now? Spaghetti mess... <?php $all_tables_query  = ' SELECT table_name, MAX(version) as version FROM ...'; $all_tables_result =  PMA_query_as_controluser($all_tables_query); // If a HEAD version exists if (PMA_DBI_num_rows($all_tables_result) > 0) { ?>     <div id="tracked_tables">     <h3><?php echo __('Tracked tables');?></h3> <?php }
Testing untestable code What else? Generative Programming
Testing untestable code Generative Programming Configuration Configuration (DSL) (DSL) 1..n Implementation- Implementation- components Generator Generator Product components Product
Testing untestable code Generative Programming Configuration Configuration (DSL) (DSL) Customer 22 Customer Implementation- Implementation- components Generator Generator Customer 11 components Customer
Testing untestable code Generative Programming Configuration Configuration (DSL) (DSL) Test Test Enviroment Enviroment Implementation- Implementation- components Generator Generator Prod. Prod. components Enviroment Enviroment
Testing untestable code Generative Programming A frame is a data structure for representing knowledge.
Testing untestable code Frame <?php class Car { private $Engine; public function __construct($sEngine) { $this­>Engine = <!{Factory}!>:: getByType($sEngine); } }
Testing untestable code ContentProvider for the Frame public class MyContentProvider extends     AbstractContentProvider {     public SlotConfiguration computeSlots(         FeatureConfiguration config) {         SlotConfiguration sl = new SlotConfiguration();         if(config.hasFeature("unittest")) {             sl.put("Factory", "FactoryMock");         } else {             sl.put("Factory", "EngineFactory");         }         return sl;     } }
Testing untestable code Generated result – Test Enviroment <?php class Car { private $Engine; public function __construct($sEngine) { $this­>Engine = FactoryMock:: getByType($sEngine); } }
Testing untestable code Generated result – Prod. Enviroment <?php class Car { private $Engine; public function __construct($sEngine) { $this­>Engine = EngineFactory:: getByType($sEngine); } }
Testing untestable code Curious for more? http://replicatorframework.org
Thank you!
http://joind.in/7972
Testing untestable code Flickr Credits http://www.flickr.com/photos/andresrueda/3452940751/ http://www.flickr.com/photos/andresrueda/3455410635/

Testing untestable code - ConFoo13

  • 1.
    Testing untestable code StephanHochdörfer, bitExpert AG
  • 2.
    Testing untestable code About me  Stephan Hochdörfer  Head of IT at bitExpert AG, Germany  enjoying PHP since 1999  S.Hochdoerfer@bitExpert.de  @shochdoerfer
  • 3.
    Testing untestable code No excuse for writing bad code!
  • 6.
    Testing untestable code "Hang the rules. They're more like guidelines anyway." Elizabeth Swann, Pirates of the Caribbean
  • 7.
    Testing untestable code "There is no secret to writing tests, there are only secrets to write testable code!" Miško Hevery
  • 8.
    Testing untestable code What is „untestable code“?
  • 14.
    Testing untestable code "...our test strategy requires us to have more control [...] of the sut." Gerard Meszaros, xUnit Test Patterns: Refactoring Test Code
  • 15.
    Testing untestable code In a perfect world... Unittest Unittest SUT SUT
  • 16.
    Testing untestable code Legacy code is not perfect... Dependency Dependency Unittest Unittest SUT SUT Dependency Dependency
  • 17.
    Testing untestable code ... Legacy code is not perfect... Dependency Dependency Unittest Unittest SUT SUT Dependency Dependency ...
  • 18.
    Testing untestable code ... Legacy code is not perfect... Dependency Dependency Unittest Unittest SUT SUT Dependency Dependency ...
  • 19.
    Testing untestable code How to get „testable“ code?
  • 20.
    Testing untestable code How to get „testable“ code? Refactoring
  • 21.
    Testing untestable code "Before you start refactoring, check that you have a solid suite of tests." Martin Fowler, Refactoring
  • 23.
    Testing untestable code Which path to take?
  • 24.
    Testing untestable code Which path to take? Do not change existing code!
  • 25.
    Testing untestable code Examples Object Construction External resources Language issues
  • 26.
    Testing untestable code Object construction <?php class Car { private $Engine; public function __construct($sEngine) { $this­>Engine = Engine::getByType($sEngine); } }
  • 27.
    Testing untestable code Object construction - 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();
  • 28.
    Testing untestable code Object construction <?php include('Engine.php'); class Car { private $Engine; public function __construct($sEngine) { $this­>Engine = Engine::getByType($sEngine); } }
  • 29.
    Testing untestable code Object construction - 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();
  • 30.
    Testing untestable code Object construction – Stream Wrapper <?php class CustomWrapper {   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', 'CustomWrapper');     return true;   } }
  • 31.
    Testing untestable code Object construction – Stream Wrapper stream_wrapper_unregister('file'); stream_wrapper_register('file', 'CustomWrapper');
  • 32.
    Testing untestable code Object construction – Stream Wrapper <?php class CustomWrapper { private $_handler; function stream_read($count) { $content = fread($this­>_handler, $count); $content = str_replace('Engine::getByType',        'AbstractEngine::get', $content); return $content; } }
  • 33.
    Testing untestable code External resources
  • 34.
    Testing untestable code External resources Database Webservice Filesystem Mailserver
  • 35.
    Testing untestable code External resources – Mock database
  • 36.
    Testing untestable code External resources – Mock database Provide own implementation
  • 37.
    Testing untestable code External resources – Mock database ZF1 example: $db = new Custom_Db_Adapter(array()); Zend_Db_Table::setDefaultAdapter($db);
  • 38.
    Testing untestable code External resources – Mock database PHPUnit_Extensions_Database_TestCase
  • 39.
    Testing untestable code External resources – Mock database require_once  "PHPUnit/Extensions/Database/TestCase.php"; class MySampleTest extends  PHPUnit_Extensions_Database_TestCase {     public function getConnection() {         $pdo = new PDO('sqlite::memory:');         return $this­>createDefaultDBConnection         $pdo, ':memory:');     }     public function getDataSet() {         return $this­>createFlatXMLDataSet(         dirname(__FILE__).'/_files/data.xml'     );     } }
  • 40.
    Testing untestable code External resources – Mock database Proxy for your SQL Server
  • 41.
    Testing untestable code External resources – Mock webservice
  • 42.
    Testing untestable code External resources – Mock webservice Provide own implementation
  • 43.
    Testing untestable code External resources – Mock webservice Host redirect via /etc/hosts
  • 44.
    Testing untestable code External resources – Mock filesystem
  • 45.
    Testing untestable code External resources – Mock filesystem <?php // set up test environmemt vfsStream::setup('exampleDir'); // create directory in test enviroment mkdir(vfsStream::url('exampleDir').'/sample/'); // check if directory was created echo vfsStreamWrapper::getRoot()­>hasChild('sample');
  • 46.
    Testing untestable code External resources – Mock Mailserver
  • 47.
    Testing untestable code External resources – Mock Mailserver Use fake mail server
  • 48.
    Testing untestable code External resources – Mock Mailserver $ cat /etc/php5/php.ini | grep sendmail_path sendmail_path=/usr/local/bin/logmail $ cat /usr/local/bin/logmail cat >> /tmp/logmail.log
  • 49.
    Testing untestable code Dealing with language issues
  • 50.
    Testing untestable code Dealing with language issues Testing your privates?
  • 51.
    Testing untestable code Dealing with language issues <?php class CustomWrapper { private $_handler; function stream_read($count) { $content = fread($this­>_handler, $count); $content = str_replace(          'private function',          'public function',           $content       ); return $content; } }
  • 52.
    Testing untestable code Dealing with language issues $myClass = new MyClass(); $reflectionClass  = new ReflectionClass('MyClass'); $reflectionMethod = $reflectionClass­> getMethod('mydemo'); $reflectionMethod­>setAccessible(true); $reflectionMethod­>invoke($myClass);
  • 53.
    Testing untestable code Dealing with language issues Overwrite internal functions?
  • 54.
    Testing untestable code Dealing with language issues pecl install runkit-0.9
  • 55.
    Testing untestable code Dealing with language issues - Runkit <?php ini_set('runkit.internal_override', '1'); runkit_function_redefine('mail','','return  true;'); ?>
  • 56.
    Testing untestable code Dealing with language issues pecl install funcall-0.3.0alpha
  • 57.
    Testing untestable code Dealing with language issues - Funcall <?php function my_func($arg1, $arg2) {     return $arg1.$arg2; } function post_cb($args,$result, $process_time) {   // return custom result based on  $args } fc_add_post('my_func','post_cb'); var_dump(my_func('php', 'c'));
  • 58.
    Testing untestable code Dealing with language issues funcall for methods?
  • 59.
    Testing untestable code Dealing with language issues git clone https://github/juliens/AOP
  • 60.
    Testing untestable code Dealing with language issues - AOP <?php aop_add_after('Car::drive*',  'adviceForDrive');
  • 61.
    Testing untestable code Dealing with language issues - AOP <?php $advice = function(AopTriggeredJoinpoint $jp) {   $returnValue =       $jp­>getReturnedValue();   // modify the return value   $returnValue = 1234;   $jp­>setReturnedValue($returnValue); }; aop_add_after('Car­>drive()', $advice);
  • 62.
    Testing untestable code And now? Spaghetti mess... <?php $all_tables_query  = ' SELECT table_name, MAX(version) as version FROM ...'; $all_tables_result =  PMA_query_as_controluser($all_tables_query); // If a HEAD version exists if (PMA_DBI_num_rows($all_tables_result) > 0) { ?>     <div id="tracked_tables">     <h3><?php echo __('Tracked tables');?></h3> <?php }
  • 65.
    Testing untestable code What else? Generative Programming
  • 66.
    Testing untestable code Generative Programming Configuration Configuration (DSL) (DSL) 1..n Implementation- Implementation- components Generator Generator Product components Product
  • 67.
    Testing untestable code Generative Programming Configuration Configuration (DSL) (DSL) Customer 22 Customer Implementation- Implementation- components Generator Generator Customer 11 components Customer
  • 68.
    Testing untestable code Generative Programming Configuration Configuration (DSL) (DSL) Test Test Enviroment Enviroment Implementation- Implementation- components Generator Generator Prod. Prod. components Enviroment Enviroment
  • 69.
    Testing untestable code Generative Programming A frame is a data structure for representing knowledge.
  • 70.
    Testing untestable code Frame <?php class Car { private $Engine; public function __construct($sEngine) { $this­>Engine = <!{Factory}!>:: getByType($sEngine); } }
  • 71.
    Testing untestable code ContentProvider for the Frame public class MyContentProvider extends     AbstractContentProvider {     public SlotConfiguration computeSlots(         FeatureConfiguration config) {         SlotConfiguration sl = new SlotConfiguration();         if(config.hasFeature("unittest")) {             sl.put("Factory", "FactoryMock");         } else {             sl.put("Factory", "EngineFactory");         }         return sl;     } }
  • 72.
    Testing untestable code Generated result – Test Enviroment <?php class Car { private $Engine; public function __construct($sEngine) { $this­>Engine = FactoryMock:: getByType($sEngine); } }
  • 73.
    Testing untestable code Generated result – Prod. Enviroment <?php class Car { private $Engine; public function __construct($sEngine) { $this­>Engine = EngineFactory:: getByType($sEngine); } }
  • 74.
    Testing untestable code Curious for more? http://replicatorframework.org
  • 75.
  • 76.
  • 77.
    Testing untestable code Flickr Credits http://www.flickr.com/photos/andresrueda/3452940751/ http://www.flickr.com/photos/andresrueda/3455410635/