"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
Testing untestable Code - PFCongres 2010
1. 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
2. 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
S.Hochdoerfer@bitExpert.de
3. Agenda
1. Theory
2. How to test untestable PHP Code
3. Generate testable code
4. Conclusion
4. Theory
"There is no secret to writing tests, there
are only secrets to write testable code!"
Miško Hevery
6. 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
7. 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
8. Theory
Required
class
Class to
Unittest
Test
Required
class
9. Theory
Database
Required
class
External
Class to
Unittest resource
test
Required
class
Required Required
Webservice
class class
10. Theory
Database
Required
class
External
Class to
Unittest resource
test
Required
class
Required Required
Webservice
class class
17. Testing „untestable“ PHP Code | __autoload
<?php
class Car {
private $Engine;
public function __construct($sEngine) {
$this->Engine = Engine::getByType($sEngine);
}
}
18. Testing „untestable“ PHP Code | __autoload
<?php
class Car {
private $Engine;
public function __construct($sEngine) {
$this->Engine = Engine::getByType($sEngine);
}
}
How to inject a dependency?
Use __autoload
20. Testing „untestable“ PHP Code | include_path
<?php
include('Engine.php');
class Car {
private $Engine;
public function __construct($sEngine) {
$this->Engine = Engine::getByType($sEngine);
}
}
21. Testing „untestable“ PHP Code | include_path
<?php
include('Engine.php');
class Car {
private $Engine;
public function __construct($sEngine) {
$this->Engine = Engine::getByType($sEngine);
}
}
How to inject a dependency?
Manipulate include_path setting
23. Testing „untestable“ PHP Code | include_path alternative
<?php
class 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
24. Testing „untestable“ PHP Code | Namespaces
<?php
class Car {
private $Engine;
public function __construct($sEngine) {
$this->Engine = CarEngine::getByType($sEngine);
}
}
25. Testing „untestable“ PHP Code | Namespaces
<?php
class Car {
private $Engine;
public function __construct($sEngine) {
$this->Engine = CarEngine::getByType($sEngine);
}
}
How to inject a dependency?
Use __autoload or manipulate the include_path
26. Testing „untestable“ PHP Code | vfsStream
<?php
class Car {
private $Engine;
public function __construct($sEngine, $CacheDir) {
$this->Engine = CarEngine::getByType($sEngine);
mkdir($CacheDir.'/cache/', 0700, true);
}
}
27. Testing „untestable“ PHP Code | vfsStream
<?php
class Car {
private $Engine;
public function __construct($sEngine, $CacheDir) {
$this->Engine = CarEngine::getByType($sEngine);
mkdir($CacheDir.'/cache/', 0700, true);
}
}
How mock a filesystem?
Use vfsStream - http://code.google.com/p/bovigo/
30. 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
31. Testing „untestable“ PHP Code | Test functions
<?php
function startsWith($sString, $psPre) {
return $psPre == substr($sString, 0, strlen($psPre));
}
function contains($sString, $sSearch) {
return false !== strpos($sString, $sSearch);
}
32. Testing „untestable“ PHP Code | Test functions
<?php
function startsWith($sString, $psPre) {
return $psPre == substr($sString, 0, strlen($psPre));
}
function contains($sString, $sSearch) {
return false !== strpos($sString, $sSearch);
}
How to test
PHPUnit can call methods
PHPUnit can save/restore globale state
35. Testing „untestable“ PHP Code | overwrite internal functions
<?php
function buyCar(Car $oCar) {
global $oDB;
mysql_query("INSERT INTO...", $oDB);
mail('order@domain.org', 'New sale', '....');
}
How to test
Do not load mysql extension. Provide own implementation
Unfortunatly mail() is part of the PHP core
36. Testing „untestable“ PHP Code | overwrite internal functions
<?php
function buyCar(Car $oCar) {
global $oDB;
mysql_query("INSERT INTO...", $oDB);
mail('order@domain.org', 'New sale', '....');
}
How to test
Use classkit extension to mock internal functions
41. Generate testable code
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
43. Generate testable code
1. Example
Prefix: test_
<?php
function buyCar(Car $oCar) {
global $oDB;
test_mysql_query("INSERT INTO...", $oDB);
}
44. Generate testable code
1. Example
Prefix: test_
<?php
function buyCar(Car $oCar) {
global $oDB;
test_mysql_query("INSERT INTO...", $oDB);
}
2. Example
MailSlot: mail('order@domain.org', 'New sale', '....');
<?php
function buyCar(Car $oCar) {
global $oDB;
mysql_query("INSERT INTO...", $oDB);
mail('order@domain.org', 'New sale', '....');
}
?>
45. 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