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
Testing untestable codeStephan 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
@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“?
s
Theory
What is „untestable code“?
„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
Class toTest
Class toTestUnittest
Unittest
Requiredclass
Requiredclass
Requiredclass
Requiredclass
Theory
Class totest
Class totestUnittest
Unittest
Requiredclass
Requiredclass
Requiredclass
Requiredclass
DatabaseDatabase
Externalresource
Externalresource
Requiredclass
RequiredclassRequired
class
Requiredclass Webservice
Webservice
Theory
Class totest
Class totestUnittest
Unittest
Requiredclass
Requiredclass
Requiredclass
Requiredclass
DatabaseDatabase
Externalresource
Externalresource
Requiredclass
RequiredclassRequired
class
Requiredclass Webservice
Webservice
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
<?phpclass Car {
private $Engine;
public function __construct($sEngine) {$this->Engine = Engine::getByType($sEngine);
}
}
Testing „untestable“ PHP Code | __autoload
How to inject a dependency? Use __autoload
<?phpclass Car {
private $Engine;
public function __construct($sEngine) {$this->Engine = Engine::getByType($sEngine);
}
}
Testing „untestable“ PHP Code | __autoload
<?phpfunction run_autoload($psClass) {
$sFileToInclude = strtolower($psClass).'.php';if(strtolower($psClass) == 'engine') {
$sFileToInclude = '/custom/mocks/'.$sFileToInclude;}include($sFileToInclude);
}
// Testcasespl_autoload_register('run_autoload');$oCar = new Car('Diesel');echo $oCar->run();
Testing „untestable“ PHP Code | include_path
<?phpinclude('Engine.php');
class Car {private $Engine;
public function __construct($sEngine) {$this->Engine = Engine::getByType($sEngine);
}}
Testing „untestable“ PHP Code | include_path
<?phpinclude('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
<?phpini_set('include_path',
'/custom/mocks/'.PATH_SEPARATOR.ini_get('include_path'));
// Testcaseinclude('car.php');
$oCar = new Car('Diesel');echo $oCar->run();
Testing „untestable“ PHP Code | include_path alternative
<?phpinclude('Engine.php');
class Car {private $Engine;
public function __construct($sEngine) {$this->Engine = Engine::getByType($sEngine);
}}
Testing „untestable“ PHP Code | include_path alternative
<?phpinclude('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
<?phpclass 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
<?phpclass 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
<?phpclass 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
<?phpclass Car {
private $Engine;
public function __construct($sEngine) {$this->Engine = \Car\Engine::getByType($sEngine);
}}
Testing „untestable“ PHP Code | Namespaces
<?phpclass Car {
private $Engine;
public function __construct($sEngine) {$this->Engine = \Car\Engine::getByType($sEngine);
}}
How to inject a dependency? Use __autoload or manipulate the include_path
Testing „untestable“ PHP Code | vfsStream
<?phpclass Car {
private $Engine;
public function __construct($sEngine, $CacheDir) {$this->Engine = \Car\Engine::getByType($sEngine);
mkdir($CacheDir.'/cache/', 0700, true);}
}
Testing „untestable“ PHP Code | vfsStream
How mock a filesystem? Use vfsStream - http://code.google.com/p/bovigo/
<?phpclass Car {
private $Engine;
public function __construct($sEngine, $CacheDir) {$this->Engine = \Car\Engine::getByType($sEngine);
mkdir($CacheDir.'/cache/', 0700, true);}
}
Testing „untestable“ PHP Code | vfsStream
<?php
// setup vfsStreamvfsStreamWrapper::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
<?phpfunction 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
How to test PHPUnit can call functions
<?phpfunction 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
How to test PHPUnit can call functions
PHPUnit can save/restore globale state
<?phpfunction startsWith($sString, $psPre) {
return $psPre == substr($sString, 0, strlen($psPre));}
function contains($sString, $sSearch) {return false !== strpos($sString, $sSearch);
}
Testing „untestable“ PHP Code | overwrite internal functions
<?phpfunction buyCar(Car $oCar) {
global $oDB;
mysql_query("INSERT INTO...", $oDB);
mail('[email protected]', 'New sale', '....');}
Testing „untestable“ PHP Code | overwrite internal functions
<?phpfunction buyCar(Car $oCar) {
global $oDB;
mysql_query("INSERT INTO...", $oDB);
mail('[email protected]', 'New sale', '....');}
Testing „untestable“ PHP Code | overwrite internal functions
<?phpfunction buyCar(Car $oCar) {
global $oDB;
mysql_query("INSERT INTO...", $oDB);
mail('[email protected]', 'New sale', '....');}
How to test Unfortunatley mail() is part of the PHP core and cannot be unloaded
Testing „untestable“ PHP Code | overwrite internal functions
<?phpfunction buyCar(Car $oCar) {
global $oDB;
mysql_query("INSERT INTO...", $oDB);
mail('[email protected]', '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
What else?
Generating testable code
Remember: No changes to the source code!
What else?
Generating testable code
Generative Programming
Generating testable code
Generative Programming
ConfigurationConfiguration
Implementationcomponents
Implementationcomponents
Generatorapplication
Generatorapplication
ProductProductGenerator
Generator
1 ... n
Generating testable code
Generative Programming
ConfigurationConfiguration
Implementationcomponents
Implementationcomponents
Generatorapplication
Generatorapplication
ApplicationApplication
GeneratorGenerator
TestcasesTestcases
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('[email protected]', 'New sale', '....');";
public FILEIndex_php5() {setFilename("index.php5");setRelativePath("/");
}
private void assign() {BEGINCONTENT()<?phpfunction 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('[email protected]', 'New sale', '....');";
public FILEIndex_php5() {setFilename("index.php5");setRelativePath("/");
}
private void assign() {BEGINCONTENT()<?phpfunction 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
ExampleMailSlot: mail('[email protected]', 'New sale', '....');
Generating testable code
Generative Programming
Extraction Show / hide parts of the code
ExampleMailSlot: mail('[email protected]', 'New sale', '....');
<?phpfunction buyCar(Car $oCar) { global $oDB;
mysql_query("INSERT INTO...", $oDB); mail('[email protected]', 'New sale', '....');}
?>
Generating testable code
Course of action
Customizing Change content of global vars Pre/Postfixes for own functions, methods, classes
ExamplePrefix: test_
Generating testable code
Course of action
Customizing Change content of global vars Pre/Postfixes for own functions, methods, classes
ExamplePrefix: test_
<?phpfunction 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