Testing untestable Code - PFCongres 2010

2,342
-1

Published on

Published in: Technology
0 Comments
2 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
2,342
On Slideshare
0
From Embeds
0
Number of Embeds
3
Actions
Shares
0
Downloads
8
Comments
0
Likes
2
Embeds 0
No embeds

No notes for slide

Testing untestable Code - PFCongres 2010

  1. 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. 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. 3. Agenda 1. Theory 2. How to test untestable PHP Code 3. Generate testable code 4. Conclusion
  4. 4. Theory "There is no secret to writing tests, there are only secrets to write testable code!" Miško Hevery
  5. 5. Theory What is „untestable code“?
  6. 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. 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. 8. Theory Required class Class to Unittest Test Required class
  9. 9. Theory Database Required class External Class to Unittest resource test Required class Required Required Webservice class class
  10. 10. Theory Database Required class External Class to Unittest resource test Required class Required Required Webservice class class
  11. 11. Theory How to achieve „testable“ code?
  12. 12. Theory How to achieve „testable“ code? Refactoring
  13. 13. Theory "Before you start refactoring, check that you have a solid suite of tests." Martin Fowler, Refactoring
  14. 14. Agenda 1. Theory 2. Testing untestable PHP Code 3. Generate testable code 4. Conclusion
  15. 15. Agenda 1. Theory 2. Testing untestable PHP Code – Hardcoded Dependencies – Procedural code – Overwrite internal PHP functions 3. Generate testable code 4. Conclusion
  16. 16. Testing „untestable“ PHP Code Assumption Do not change existing code!
  17. 17. Testing „untestable“ PHP Code | __autoload <?php class Car { private $Engine; public function __construct($sEngine) { $this->Engine = Engine::getByType($sEngine); } }
  18. 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
  19. 19. 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();
  20. 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. 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
  22. 22. 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();
  23. 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. 24. Testing „untestable“ PHP Code | Namespaces <?php class Car { private $Engine; public function __construct($sEngine) { $this->Engine = CarEngine::getByType($sEngine); } }
  25. 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. 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. 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/
  28. 28. 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');
  29. 29. Agenda 1. Theory 2. Testing untestable PHP Code – Hardcoded Dependencies – Procedural code – Overwrite internal PHP functions 3. Generate testable code 4. Conclusion
  30. 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. 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. 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
  33. 33. Agenda 1. Theory 2. Testing untestable PHP Code – Hardcoded Dependencies – Procedural code – Overwrite internal PHP functions 3. Generate testable code 4. Conclusion
  34. 34. 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', '....'); }
  35. 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. 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
  37. 37. Testing „untestable“ PHP Code | overwrite internal functions <?php ini_set('runkit.internal_override', '1'); runkit_function_redefine('mail','','return true;'); ?>
  38. 38. Agenda 1. Theory 2. Testing untestable PHP Code 3. Generate testable code 4. Conclusion
  39. 39. Generate testable code Generative Programming Configuration 1 ... n Implementation Generator Product components Generator application
  40. 40. Generate testable code Generative Programming Configuration Application Implementation Generator components Testcases Generator application
  41. 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
  42. 42. 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() } }
  43. 43. Generate testable code 1. Example Prefix: test_ <?php function buyCar(Car $oCar) { global $oDB; test_mysql_query("INSERT INTO...", $oDB); }
  44. 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. 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

×