Testing untestable Code - PFCongres 2010
Upcoming SlideShare
Loading in...5
×
 

Testing untestable Code - PFCongres 2010

on

  • 3,005 views

 

Statistics

Views

Total Views
3,005
Views on SlideShare
2,993
Embed Views
12

Actions

Likes
2
Downloads
7
Comments
0

2 Embeds 12

http://www.slideshare.net 11
http://www.thewebhatesme.com 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Testing untestable Code - PFCongres 2010 Testing untestable Code - PFCongres 2010 Presentation Transcript

  • 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
  • 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
  • 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 Required class Class to Unittest Test Required class
  • Theory Database Required class External Class to Unittest resource test Required class Required Required Webservice class class
  • Theory Database Required class External Class to Unittest resource test Required class Required Required Webservice class class
  • 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 <?php class Car { private $Engine; public function __construct($sEngine) { $this->Engine = Engine::getByType($sEngine); } }
  • 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
  • Testing „untestable“ PHP Code | __autoload <?php function run_autoload($psClass) { $sFileToInclude = strtolower($psClass).'.php'; if(strtolower($psClass) == 'engine') { $sFileToInclude = '/custom/mocks/'.$sFileToInclude; } include($sFileToInclude); } // Testcase spl_autoload_register('run_autoload'); $oCar = new Car('Diesel'); echo $oCar->run();
  • Testing „untestable“ PHP Code | include_path <?php include('Engine.php'); class Car { private $Engine; public function __construct($sEngine) { $this->Engine = Engine::getByType($sEngine); } }
  • 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
  • Testing „untestable“ PHP Code | include_path <?php ini_set('include_path', '/custom/mocks/'.PATH_SEPARATOR. ini_get('include_path')); // Testcase include('car.php'); $oCar = new Car('Diesel'); echo $oCar->run();
  • 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
  • Testing „untestable“ PHP Code | Namespaces <?php class Car { private $Engine; public function __construct($sEngine) { $this->Engine = CarEngine::getByType($sEngine); } }
  • 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
  • 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); } }
  • 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/
  • Testing „untestable“ PHP Code | vfsStream <?php // setup vfsStream vfsStreamWrapper::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 <?php function 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 <?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
  • 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 <?php function buyCar(Car $oCar) { global $oDB; mysql_query("INSERT INTO...", $oDB); mail('order@domain.org', 'New sale', '....'); }
  • 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
  • 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
  • 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
  • Generate testable code Generative Programming Configuration 1 ... n Implementation Generator Product components Generator application
  • Generate testable code Generative Programming Configuration Application Implementation Generator components Testcases Generator application
  • 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
  • Generate testable code FileFrm FILEIndex_php5 { private String Prefix = "test_"; private String MailSlot = "mail('order@domain.org', 'New sale', '....');"; public FILEIndex_php5() { setFilename("index.php5"); setRelativePath("/"); } private void assign() { BEGINCONTENT() <?php function buyCar(Car $oCar) { global $oDB; <!{Prefix}!>mysql_query(„INSERT INTO...“, $oDB); <!{MailSlot}!> } ?> ENDCONTENT() } }
  • Generate testable code 1. Example Prefix: test_ <?php function buyCar(Car $oCar) { global $oDB; test_mysql_query("INSERT INTO...", $oDB); }
  • 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', '....'); } ?>
  • 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