Things to consider for testable Code

1,691 views

Published on

Who does *really* do Test-Driven Development? Probably only a small minority. The presentation shows what code leads to complex tests and what you have to consider to make writing tests much easier - because not doing TDD is no excuse for not having tests at all.

Published in: Technology, Business
  • Be the first to comment

  • Be the first to like this

Things to consider for testable Code

  1. 1. Things to consider for testable code Frank Kleine, 27.10.2009
  2. 2. The Speaker: Frank Kleine <ul><li>1&1 Internet AG (Head of Web Infrastructure) </li></ul><ul><li>PHP since 2000 </li></ul><ul><li>Lead Developer </li></ul><ul><ul><li>Stubbles </li></ul></ul><ul><ul><li>XJConf for PHP </li></ul></ul><ul><ul><li>vfsStream </li></ul></ul><ul><li>Technical editor &quot;PHP Design Patterns&quot; </li></ul>
  3. 3. Separation of Concerns <ul><li>Typical concerns in applications </li></ul>
  4. 4. Business logic
  5. 5. Logging
  6. 6. Error handling
  7. 7. Environment handling
  8. 8. Didn't we forget something really important?
  9. 9. Construction of Objects!
  10. 10. Construction of objects <ul><li>new is a very powerful keyword </li></ul>
  11. 11. Construction of objects <ul><li>new is a very powerful keyword </li></ul><ul><li>Binds usage to a concrete implementation </li></ul>
  12. 12. Construction of objects <ul><li>new is a very powerful keyword </li></ul><ul><li>Binds usage to a concrete implementation </li></ul><ul><li>Golden rule of new : </li></ul><ul><ul><li>OK for domain classes, but not for services </li></ul></ul>
  13. 13. Construction of objects <ul><li>new is a very powerful keyword </li></ul><ul><li>Binds usage to a concrete implementation </li></ul><ul><li>Golden rule of new : </li></ul><ul><ul><li>OK for domain classes, but not for services </li></ul></ul><ul><ul><li>OK in tests and specialised construction classes, but not in business logic </li></ul></ul>
  14. 14. Construction of objects II <ul><li>Bad: </li></ul>class Car { public function __construct() { $this->engine = new Engine(); $this->tire = TireFactory::createTire(); } }
  15. 15. Construction of objects II <ul><li>Bad: </li></ul>class Car { public function __construct() { $this->engine = new Engine(); $this->tire = TireFactory::createTire(); } } Work in constructor (Anti-Pattern)
  16. 16. Construction of objects II <ul><li>Bad: </li></ul>class Car { public function __construct() { $this->engine = new Engine(); $this->tire = TireFactory::createTire(); } } Work in constructor (Anti-Pattern) Hard to test
  17. 17. Construction of objects II <ul><li>Bad: </li></ul><ul><li>Good: </li></ul>class Car { public function __construct() { $this->engine = new Engine(); $this->tire = TireFactory::createTire(); } } class Car { public function __construct(Engine $eng, Tire $tire) { $this->engine = $eng; $this->tire = $tire; } } Work in constructor (Anti-Pattern) Hard to test
  18. 18. Construction of objects II <ul><li>Bad: </li></ul><ul><li>Good: </li></ul>class Car { public function __construct() { $this->engine = new Engine(); $this->tire = TireFactory::createTire(); } } class Car { public function __construct(Engine $eng, Tire $tire) { $this->engine = $eng; $this->tire = $tire; } } Work in constructor (Anti-Pattern) Hard to test Piece of cake to test
  19. 19. Construction of objects II <ul><li>Bad: </li></ul><ul><li>Good: </li></ul>class Car { public function __construct() { $this->engine = new Engine(); $this->tire = TireFactory::createTire(); } } class Car { public function __construct(Engine $eng, Tire $tire) { $this->engine = $eng; $this->tire = $tire; } } Work in constructor (Anti-Pattern) Dependency Injection Hard to test Piece of cake to test
  20. 20. Dependency Injection <ul><li>Pile of new (startup phase) </li></ul><ul><ul><li>Create objects and stack them together </li></ul></ul><ul><ul><li>&quot;Boilerplate&quot;, but automatable with DI framework </li></ul></ul><ul><li>Pile of objects (runtime phase) </li></ul><ul><ul><li>Business logic, error handling, etc. </li></ul></ul><ul><li>Factories or DI instances for runtime object creation </li></ul>
  21. 21. <ul><li>Dependency Injection is viral </li></ul>
  22. 22. Law of Demeter <ul><li>Bad: </li></ul>class Driver { public function drive($miles) { $this->vehicle->engine->start(); $this->vehicle->drive($miles); $this->vehicle->engine->stop(); }
  23. 23. Law of Demeter <ul><li>Bad: </li></ul>class Driver { public function drive($miles) { $this->vehicle->engine->start(); $this->vehicle->drive($miles); $this->vehicle->engine->stop(); } Driver coupled to Engine
  24. 24. Law of Demeter <ul><li>Bad: </li></ul>class Driver { public function drive($miles) { $this->vehicle->engine->start(); $this->vehicle->drive($miles); $this->vehicle->engine->stop(); } Hard to test: test always needs Engine or a mock of it Driver coupled to Engine
  25. 25. Law of Demeter <ul><li>Bad: </li></ul>class Driver { public function drive($miles) { $this->vehicle->engine->start(); $this->vehicle->drive($miles); $this->vehicle->engine->stop(); } Internal state of Vehicle revealed Driver coupled to Engine Hard to test: test always needs Engine or a mock of it
  26. 26. Law of Demeter <ul><li>Bad: </li></ul><ul><li>Good: </li></ul>class Driver { public function drive($miles) { $this->vehicle->engine->start(); $this->vehicle->drive($miles); $this->vehicle->engine->stop(); } Internal state of Vehicle revealed class Driver { public function drive($miles) { $this->vehicle->start(); $this->vehicle->drive($miles); $this->vehicle->stop(); } Hard to test: test always needs Engine or a mock of it Driver coupled to Engine
  27. 27. Law of Demeter <ul><li>Bad: </li></ul><ul><li>Good: </li></ul>class Driver { public function drive($miles) { $this->vehicle->engine->start(); $this->vehicle->drive($miles); $this->vehicle->engine->stop(); } Internal state of Vehicle revealed class Driver { public function drive($miles) { $this->vehicle->start(); $this->vehicle->drive($miles); $this->vehicle->stop(); } Driver coupled to Engine Hard to test: test always needs Engine or a mock of it Driver not coupled to Engine: simpler to maintain
  28. 28. Law of Demeter <ul><li>Bad: </li></ul><ul><li>Good: </li></ul>class Driver { public function drive($miles) { $this->vehicle->engine->start(); $this->vehicle->drive($miles); $this->vehicle->engine->stop(); } Internal state of Vehicle revealed class Driver { public function drive($miles) { $this->vehicle->start(); $this->vehicle->drive($miles); $this->vehicle->stop(); } Piece of cake to test Hard to test: test always needs Engine or a mock of it Driver coupled to Engine Driver not coupled to Engine: simpler to maintain
  29. 29. Law of Demeter <ul><li>Bad: </li></ul><ul><li>Good: </li></ul>class Driver { public function drive($miles) { $this->vehicle->engine->start(); $this->vehicle->drive($miles); $this->vehicle->engine->stop(); } Internal state of Vehicle revealed class Driver { public function drive($miles) { $this->vehicle->start(); $this->vehicle->drive($miles); $this->vehicle->stop(); } Piece of cake to test Less prone to errors Driver coupled to Engine Hard to test: test always needs Engine or a mock of it Driver not coupled to Engine: simpler to maintain
  30. 30. Global state <ul><li>Same operation with same inputs should yield same results. </li></ul><ul><li>Not necessarily true with global state. </li></ul>
  31. 31. Global state <ul><li>Same operation with same inputs should yield same results. </li></ul><ul><li>Not necessarily true with global state. </li></ul><ul><li>Hidden global state </li></ul><ul><ul><li>Singleton </li></ul></ul><ul><ul><li>static methods </li></ul></ul><ul><ul><li>$_GET, $_POST, $_SESSION, $_... </li></ul></ul><ul><ul><li>Registry </li></ul></ul>
  32. 32. Global state: Singletons <ul><li>Never implement any </li></ul><ul><li>Required most likely due to bad design of your framework </li></ul>
  33. 33. Global state: Singletons <ul><li>Never implement any </li></ul><ul><li>Required most likely due to bad design of your framework </li></ul><ul><li>Use a DI framework with support for Singleton scope </li></ul><ul><ul><li>Gives full power of Singletons </li></ul></ul><ul><ul><li>Code using the Singleton stays simple </li></ul></ul>
  34. 34. Singleton with DI framework $binder->bind('Session') ->to('PhpSession')
  35. 35. Singleton with DI framework $binder->bind('Session') ->to('PhpSession') Configure the binding
  36. 36. Singleton with DI framework $binder->bind('Session') ->to('PhpSession') ->in(stubBindingScopes::$SINGLETON); Configure the binding Enforce singletoness: DI framework will only create one instance of PhpSession and inject always this same instance
  37. 37. Singleton with DI framework $binder->bind('Session') ->to('PhpSession') ->in(stubBindingScopes::$SINGLETON); class Processor { protected $session; /** * @Inject */ public function __construct(Session $session) { $this->session = $session; } … } Configure the binding Enforce singletoness: DI framework will only create one instance of PhpSession and inject always this same instance
  38. 38. Singleton with DI framework $binder->bind('Session') ->to('PhpSession') ->in(stubBindingScopes::$SINGLETON); class Processor { protected $session; /** * @Inject */ public function __construct(Session $session) { $this->session = $session; } … } Configure the binding Enforce singletoness: DI framework will only create one instance of PhpSession and inject always this same instance Dependency Injection
  39. 39. Singleton with DI framework $binder->bind('Session') ->to('PhpSession') ->in(stubBindingScopes::$SINGLETON); class Processor { protected $session; /** * @Inject */ public function __construct(Session $session) { $this->session = $session; } … } Configure the binding Enforce singletoness: DI framework will only create one instance of PhpSession and inject always this same instance Dependency Injection Tell DI framework to inject required parameters on creation of Processor
  40. 40. Singleton with DI framework $binder->bind('Session') ->to('PhpSession') ->in(stubBindingScopes::$SINGLETON); class Processor { protected $session; /** * @Inject */ public function __construct(Session $session) { $this->session = $session; } … } Configure the binding Enforce singletoness: DI framework will only create one instance of PhpSession and inject always this same instance Dependency Injection Piece of cake to test: independent of PhpSession Tell DI framework to inject required parameters on creation of Processor
  41. 41. Global state: static methods <ul><li>Not always bad </li></ul><ul><ul><li>Simple operations without dependencies </li></ul></ul><ul><li>Always bad if state is involved </li></ul><ul><ul><li>Might return different results with same input. </li></ul></ul>
  42. 42. Global state: $_GET, $_POST, … <ul><li>Ugly to test </li></ul>
  43. 43. Global state: $_GET, $_POST, … <ul><li>Ugly to test </li></ul><ul><li>Rule: never access those vars directly in business logic </li></ul>
  44. 44. Global state: $_GET, $_POST, … <ul><li>Ugly to test </li></ul><ul><li>Rule: never access those vars directly in business logic </li></ul><ul><li>Abstract access to globals with classes </li></ul><ul><ul><li>Remember: Singleton is evil </li></ul></ul>
  45. 45. Global state: $_GET, $_POST, … <ul><li>Ugly to test </li></ul><ul><li>Rule: never access those vars directly in business logic </li></ul><ul><li>Abstract access to globals with classes </li></ul><ul><ul><li>Remember: Singleton is evil </li></ul></ul><ul><li>Even better: interfaces </li></ul>
  46. 46. Global state: $_GET, $_POST, … <ul><li>Ugly to test </li></ul><ul><li>Rule: never access those vars directly in business logic </li></ul><ul><li>Abstract access to globals with classes </li></ul><ul><ul><li>Remember: Singleton is evil </li></ul></ul><ul><li>Even better: interfaces </li></ul><ul><li>Security: use a Request class which enforces filtering/validating input </li></ul>
  47. 47. Global state: Registry <ul><li>Using a DI framework: no registry required </li></ul>
  48. 48. Global state: Registry <ul><li>Using a DI framework: no registry required </li></ul><ul><li>Without DI framework: use Registry for config values only </li></ul>
  49. 49. Global state: Registry <ul><li>Using a DI framework: no registry required </li></ul><ul><li>Without DI framework: use Registry for config values only </li></ul><ul><li>Did I already mentioned that Singletons are evil? </li></ul>
  50. 50. Global state: Registry <ul><li>Using a DI framework: no registry required </li></ul><ul><li>Without DI framework: use Registry for config values only </li></ul><ul><li>Did I already mentioned that Singletons are evil? </li></ul><ul><ul><li>static methods are enough </li></ul></ul>
  51. 52. Modify
  52. 53. Simplify Modify
  53. 54. Simplify Improve Modify
  54. 55. Finally… <ul><li>If you remember only one little thing of this presentation it should be… </li></ul>
  55. 56. Singletons are really, really (and I mean really)
  56. 57. Singletons are really, really (and I mean really) EVIL
  57. 58. The End <ul><li>Questions and comments? </li></ul>
  58. 59. The End <ul><li>Questions and comments? </li></ul><ul><li>Thank you. </li></ul>
  59. 60. Commercial break <ul><li>Stephan Schmidt: PHP Design Patterns </li></ul>
  60. 61. Commercial break <ul><li>Stephan Schmidt: PHP Design Patterns </li></ul><ul><li>Clean code talks: http://www.youtube.com/view_play_list?p=79645C72EA595A91 </li></ul>
  61. 62. Commercial break <ul><li>Stephan Schmidt: PHP Design Patterns </li></ul><ul><li>Clean code talks: http://www.youtube.com/view_play_list?p=79645C72EA595A91 </li></ul><ul><li>Stubbles: www.stubbles.net </li></ul>

×