Testing untestable Code - PFCongres 2010
-
Upload
stephan-hochdoerfer -
Category
Technology
-
view
2.233 -
download
2
Transcript of Testing untestable Code - PFCongres 2010
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
Founder of bitExpert AG, Mannheim
Field of duty Department Manager of Research Labs Head of Development for bitFramework
Focusing on PHP Generative Programming
Contact me @shochdoerfer [email protected]
Agenda
1. Theory
2. How to test untestable PHP Code
3. Generate testable code
4. Conclusion
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“?
Global variables Singleton Registry Globale state
static method calls Control flow in a constructor Tight coupling
Dependencies to concrete implementations to many dependencies to other classes Dependencies without control
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
Unittest
Requiredclass
Requiredclass
Theory
Class totest
Unittest
Requiredclass
Requiredclass
Database
Externalresource
Requiredclass
Requiredclass
Webservice
Theory
Class totest
Unittest
Requiredclass
Requiredclass
Database
Externalresource
Requiredclass
Requiredclass
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
Agenda
1. Theory
2. Testing untestable PHP Code
3. Generate testable code
4. Conclusion
Agenda
1. Theory
2. Testing untestable PHP Code– Hardcoded Dependencies– Procedural code– Overwrite internal PHP functions
3. Generate testable code
4. Conclusion
Testing „untestable“ PHP Code
Assumption
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
<?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');
Source: Alex Netkachov, http://www.alexatnet.com/node/203
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');
Agenda
1. Theory
2. Testing untestable PHP Code– Hardcoded Dependencies– Procedural code– Overwrite internal PHP functions
3. Generate testable code
4. Conclusion
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 methods 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);
}
Agenda
1. Theory
2. Testing untestable PHP Code– Hardcoded Dependencies– Procedural code– Overwrite internal PHP functions
3. Generate testable code
4. Conclusion
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 Do not load mysql extension. Provide own implementation Unfortunatly mail() is part of the PHP core
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 mock internal functions
Testing „untestable“ PHP Code | overwrite internal functions
<?php
ini_set('runkit.internal_override', '1');
runkit_function_redefine('mail','','return true;');
?>
Agenda
1. Theory
2. Testing untestable PHP Code
3. Generate testable code
4. Conclusion
Generative Programming
Generate testable code
Configuration
Implementationcomponents
Generatorapplication
ProductGenerator
1 ... n
Generative Programming
Generate testable code
Configuration
Implementationcomponents
Generatorapplication
Application
Generator
Testcases
Course of action
Extraction „Mask“ parts of the code
Customizing Change content of global vars Pre/Postfixes for own functions, methods, classes
Recombine Re-order parts of the code
Generate testable code
Generate 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() }}
Generate testable code
<?phpfunction buyCar(Car $oCar) { global $oDB;
test_mysql_query("INSERT INTO...", $oDB);}
1. Example
Prefix: test_
Generate testable code
<?phpfunction buyCar(Car $oCar) { global $oDB;
test_mysql_query("INSERT INTO...", $oDB);}
<?phpfunction buyCar(Car $oCar) { global $oDB;
mysql_query("INSERT INTO...", $oDB); mail('[email protected]', 'New sale', '....');}
?>
1. Example
Prefix: test_
2. Example
MailSlot: mail('[email protected]', 'New sale', '....');
Conclustion
Conclusion
Change mindset to write testable code Dependency Injection
Look for other options to raise the bar Work around limitations of PHP PHP is flexible, use it that way