Testing untestable codeStephan Hochdörfer, bitExpert AG
Testing untestable code
About me
Stephan Hochdörfer, bitExpert AG
Department Manager Research Labs
enjoying PHP since 1999
@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
What is „untestable code“?
Testing 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
SUTSUTUnittestUnittest
In a perfect world...
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
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<?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');echo $oCar>run();
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 Wrapperstream_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
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 databaserequire_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 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
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_pathsendmail_path=/usr/local/bin/logmail
$ cat /usr/local/bin/logmailcat >> /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<?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
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
<?phpfunction 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);
<?php$all_tables_query = ' SELECT table_name, MAX(version) asversion FROM ...';$all_tables_result = PMA_query_as_controluser($all_tables_query);
// If a HEAD version existsif (PMA_DBI_num_rows($all_tables_result) > 0) {?> <div id="tracked_tables"> <h3><?php echo __('Tracked tables');?></h3><?php}
Testing untestable code
And now? Spaghetti mess...
What else?
Testing untestable code
Generative Programming
Testing untestable code
Generative Programming
Configuration(DSL)
Configuration(DSL)
Implementation-components
Implementation-components GeneratorGenerator
ProductProduct
1..n
Testing untestable code
Generative Programming
Configuration(DSL)
Configuration(DSL)
Implementation-components
Implementation-components GeneratorGenerator
Customer 1Customer 1
Customer 2Customer 2
Testing untestable code
Generative Programming
Configuration(DSL)
Configuration(DSL)
Implementation-components
Implementation-components GeneratorGenerator Prod.
Enviroment
Prod.Enviroment
TestEnviroment
TestEnviroment
Testing untestable code
Generative Programming
A frame is a data structure for representing knowledge.
Testing untestable code
Frame<?phpclass Car {
private $Engine;
public function __construct($sEngine) {$this>Engine = <!{Factory}!>::
getByType($sEngine);}
}
Testing untestable code
ContentProvider for the Framepublic 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<?phpclass Car {
private $Engine;
public function __construct($sEngine) {$this>Engine = FactoryMock::
getByType($sEngine);}
}
Testing untestable code
Generated result – Prod. Enviroment<?phpclass Car {
private $Engine;
public function __construct($sEngine) {$this>Engine = EngineFactory::
getByType($sEngine);}
}
Curious for more?
Testing untestable code
http://replicatorframework.org
Thank you!
http://joind.in/7314
Testing untestable code
Flickr Credits
http://www.flickr.com/photos/andresrueda/3452940751/
http://www.flickr.com/photos/andresrueda/3455410635/