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. Agenda
1. About me
2. Theory
3. How to test untestable code
4. Generating testable code
5. Conclusions
6. Questions
3. About me
Stephan Hochdörfer, bitExpert AG
Department Manager Research Labs
enjoying PHP since 1999
S.Hochdoerfer@bitExpert.de
@shochdoerfer
10. 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
11. Theory
Required
class
Class to
Unittest
Test
Required
class
12. Theory
Database
Required
class
External
Class to
Unittest resource
test
Required
class
Required Required
Webservice
class class
13. Theory
Database
Required
class
External
Class to
Unittest resource
test
Required
class
Required Required
Webservice
class class
19. Testing „untestable“ PHP Code | __autoload
<?php
class Car {
private $Engine;
public function __construct($sEngine) {
$this->Engine = Engine::getByType($sEngine);
}
}
20. 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
22. Testing „untestable“ PHP Code | include_path
<?php
include('Engine.php');
class Car {
private $Engine;
public function __construct($sEngine) {
$this->Engine = Engine::getByType($sEngine);
}
}
23. 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
27. Testing „untestable“ PHP Code | Namespaces
<?php
class Car {
private $Engine;
public function __construct($sEngine) {
$this->Engine = CarEngine::getByType($sEngine);
}
}
28. 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
29. 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);
}
}
30. 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/
32. 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
33. 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);
}
34. 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 functions
PHPUnit can save/restore globale state
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
Do not load mysql extension. Provide own implementation
Unfortunatley mail() is part of the PHP core
37. 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 overwrite internal functions
43. Generating 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
48. Conclusion
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