testing untestable code - phpday
TRANSCRIPT
Testing untestable code
About me
Stephan Hochdörfer, bitExpert AG
Department Manager Research Labs
enjoying PHP since 1999
@shochdoerfer
Testing untestable code
"There is no secret to writing tests, there are only secrets to write
testable code!" Miško Hevery
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
SUTSUTUnittestUnittest
Legacy code is not perfect...
DependencyDependency
DependencyDependency
Testing untestable code
SUTSUTUnittestUnittest
Legacy code is not perfect...
DependencyDependency
DependencyDependency
...
...
Testing untestable code
SUTSUTUnittestUnittest
Legacy code is not perfect...
DependencyDependency
DependencyDependency
...
...
Testing untestable code
"Before you start refactoring, check that you have a solid suite of tests."
Martin Fowler, Refactoring
Testing untestable code
Object construction
<?phpclass Car {
private $Engine;
public function __construct($sEngine) {$this->Engine = Engine::getByType($sEngine);
}
}
Testing untestable code
Object construction - 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');
Testing untestable code
Object construction
<?phpinclude('Engine.php');
class Car {private $Engine;
public function __construct($sEngine) {$this->Engine = Engine::getByType($sEngine);
}}
Testing untestable code
Object construction - 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 code
Object construction – Stream Wrapper
<?phpclass 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
<?phpclass 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
Object construction – Namespaces
<?phpclass Car {
private $Engine;
public function __construct($sEngine) {$this->Engine = \Car\Engine::
getByType($sEngine);}
}
Testing untestable code
External resources – Mock database
ZF example:
$db = new Custom_Db_Adapter(array());Zend_Db_Table::setDefaultAdapter($db);
Testing untestable code
External resources – Mock filesystem
<?php
// set up test environmemtvfsStream::setup('exampleDir');
// create directory in test enviromentmkdir(vfsStream::url('exampleDir').'/sample/');
// check if directory was createdecho vfsStreamWrapper::getRoot()->hasChild('sample');
Testing untestable code
External resources – Mock Mailserver
$ cat /etc/php5/php.ini | grep sendmail_pathsendmail_path=/usr/local/bin/logmail
$ cat /usr/local/bin/logmailcat >> /tmp/logmail.log
Testing untestable code
Dealing with language issues
<?phpclass 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 - Runkit
<?php
ini_set('runkit.internal_override', '1');
runkit_function_redefine('mail','','returntrue;');
?>
Testing untestable code
Generative Programming
ConfigurationConfiguration
Implementationcomponents
Implementationcomponents
Generatorapplication
Generatorapplication
ProductProductGeneratorGenerator
1 ... n
Testing untestable code
Generative Programming
ConfigurationConfiguration
Implementationcomponents
Implementationcomponents
Generatorapplication
Generatorapplication
GeneratorGenerator
ApplicationApplication
TestcasesTestcases
Testing untestable code
Generative Programming
A frame is a data structure for representing knowledge.
Testing untestable code
A Frame instance
<?phpclass Car {
private $Engine;
public function __construct($sEngine) {$this->Engine = <!{Factory}!>::
getByType($sEngine);}
}
Testing untestable code
The Frame controller
public class MyFrameController extendsSimpleFrameController {
public void execute(Frame frame, FeatureConfigconfig) {
if(config.hasFeature("unittest")) { frame.setSlot("Factory", "FactoryMock");}else { frame.setSlot("Factory", "EngineFactory");}
}}
Testing untestable code
Generated result - Testcase
<?phpclass Car {
private $Engine;
public function __construct($sEngine) {$this->Engine = FactoryMock::
getByType($sEngine);}
}
Testing untestable code
Generated result - Application
<?phpclass Car {
private $Engine;
public function __construct($sEngine) {$this->Engine = EngineFactory::
getByType($sEngine);}
}