Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Your code are my tests

2,057 views

Published on

After years of promoting PHPUnit I still hear it's hard to get started with unit testing. So instead of showing nice step-by-step examples on how to use PHPUnit, we're going to take an example straight from github. So I've taken the challenge to start writing tests for PHP projects that don't have unit tests in place and explain how I decide where to begin, how I approach my test strategy and how I ensure I’m covering each possible use-case (and covering the CRAP index). The goal of this presentation is to show everyone that even legacy code, spaghetti code and complex code bases can be tested. After this talk you can immediately apply my examples on your own codebase (even if it's a clean code base) and get started with testing. To follow along a basic knowledge unit testing with PHPUnit is required.

Published in: Engineering
  • D0WNL0AD FULL ▶ ▶ ▶ ▶ http://1url.pw/3oSb7 ◀ ◀ ◀ ◀
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

Your code are my tests

  1. 1. Your code are my tests How to test legacy code in it2PROFESSIONAL PHP SERVICES
  2. 2. ADVISORY IN ORDER TO EXPLAIN CERTAIN SITUATIONS YOU MIGHT FACE IN YOUR DEVELOPMENT CAREER, WE WILL BE DISCUSSING THE USAGE OF PRIVATES AND PUBLIC EXPOSURE. IF THESE TOPICS OFFEND OR UPSET YOU, WE WOULD LIKE TO ASK YOU TO LEAVE THIS ROOM NOW. THE SPEAKER NOR THE ORGANISATION CANNOT BE HELD ACCOUNTABLE FOR MENTAL DISTRESS OR ANY FORMS OF DAMAGE YOU MIGHT ENDURE DURING OR AFTER THIS PRESENTATION. FOR COMPLAINTS PLEASE INFORM ORGANISATION AT INFO@IN2IT.BE.
  3. 3. Michelangelo van Dam PHP Consultant Community Leader President of PHPBenelux Contributor to PHP projects T @DragonBe | F DragonBe https://www.flickr.com/photos/akrabat/8784318813
  4. 4. Using Social Media? Tag it #mytests http://www.flickr.com/photos/andyofne/4633356197 http://www.flickr.com/photos/andyofne/4633356197
  5. 5. Why bother with testing? https://www.flickr.com/photos/vialbost/5533266530
  6. 6. Most common excuses why developers don’t test • no time • no budget • deliver tests after finish project (never) • devs don’t know how https://www.flickr.com/photos/dasprid/8147986307
  7. 7. No excuses!!! Crea%ve  Commons  -­‐  h.p://www.flickr.com/photos/akrabat/8421560178
  8. 8. Responsibility issue • As a developer, it’s your job to • write code & fixing bugs • add documentation • write & update unit tests
  9. 9. Pizza principleTopping:  your  tests Box:  your  documenta%on Dough:  your  code
  10. 10. Benefits of testing • Direct feedback (test fails) • Once a test is made, it will always be tested • Easy to refactor existing code (protection) • Easy to debug: write a test to see if a bug is genuine • Higher confidence and less uncertainty
  11. 11. Rule of thumb “Whenever you are tempted to type something into a print statement or a debugger expression, write it as a test instead.” — Source: Martin Fowler
  12. 12. Warming up https://www.flickr.com/photos/bobjagendorf/8535316836
  13. 13. PHPUnit • PHPUnit is a port of xUnit testing framework • Created by “Sebastian Bergmann” • Uses “assertions” to verify behaviour of “unit of code” • Open source and hosted on GitHub • See https://github.com/sebastianbergmann/phpunit • Can be installed using: • PEAR • PHAR • Composer
  14. 14. Approach for testing • Instantiate a “unit-of-code” • Assert expected result against actual result • Provide a custom error message
  15. 15. Available assertions • assertArrayHasKey() • assertClassHasAttribute() • assertClassHasStaticAttribute() • assertContains() • assertContainsOnly() • assertContainsOnlyInstancesOf() • assertCount() • assertEmpty() • assertEqualXMLStructure() • assertEquals() • assertFalse() • assertFileEquals() • assertFileExists() • assertGreaterThan() • assertGreaterThanOrEqual() • assertInstanceOf() • assertInternalType() • assertJsonFileEqualsJsonFile() • assertJsonStringEqualsJsonFile() • assertJsonStringEqualsJsonString() • assertLessThan() • assertLessThanOrEqual() • assertNull() • assertObjectHasAttribute() • assertRegExp() • assertStringMatchesFormat() • assertStringMatchesFormatFile() • assertSame() • assertSelectCount() • assertSelectEquals() • assertSelectRegExp() • assertStringEndsWith() • assertStringEqualsFile() • assertStringStartsWith() • assertTag() • assertThat() • assertTrue() • assertXmlFileEqualsXmlFile() • assertXmlStringEqualsXmlFile() • assertXmlStringEqualsXmlString()
  16. 16. To  protect  and  to  serve
  17. 17. Data is tainted, ALWAYS Hackers BAD DATA Web Services Stupid users
  18. 18. OWASP top 10 exploits https://www.owasp.org/index.php/Top_10_2013-Top_10
  19. 19. Filtering & Validation
  20. 20. Smallest unit of code https://www.flickr.com/photos/toolstop/4546017269
  21. 21. Example class <?php /**  * Example class  */ class MyClass {     /** ... */     public function doSomething($requiredParam, $optionalParam = null)     {         if (!filter_var(             $requiredParam, FILTER_SANITIZE_STRING, FILTER_FLAG_ENCODE_HIGH         )) {             throw new InvalidArgumentException('Invalid argument provided');         }         if (null !== $optionalParam) {             if (!filter_var(                 $optionalParam, FILTER_SANITIZE_STRING, FILTER_FLAG_ENCODE_HIGH             )) {                 throw new InvalidArgumentException('Invalid argument provided');             }             $requiredParam .= ' - ' . $optionalParam;         }         return $requiredParam;     } }
  22. 22. Testing for good    /** ... */     public function testClassAcceptsValidRequiredArgument()     {         $expected = $argument = 'Testing PHP Class';         $myClass = new MyClass;         $result = $myClass->doSomething($argument);         $this->assertSame($expected, $result,              'Expected result differs from actual result');     }    /** ... */         public function testClassAcceptsValidOptionalArgument()     {         $requiredArgument = 'Testing PHP Class';         $optionalArgument = 'Is this not fun?!?';         $expected = $requiredArgument . ' - ' . $optionalArgument;         $myClass = new MyClass;         $result = $myClass->doSomething($requiredArgument, $optionalArgument);         $this->assertSame($expected, $result,              'Expected result differs from actual result');     }
  23. 23. Testing for bad     /**      * @expectedException InvalidArgumentException      */     public function testExceptionIsThrownForInvalidRequiredArgument()     {         $expected = $argument = new StdClass;         $myClass = new MyClass;         $result = $myClass->doSomething($argument);         $this->assertSame($expected, $result,              'Expected result differs from actual result');     }          /**      * @expectedException InvalidArgumentException      */     public function testExceptionIsThrownForInvalidOptionalArgument()     {         $requiredArgument = 'Testing PHP Class';         $optionalArgument = new StdClass;         $myClass = new MyClass;         $result = $myClass->doSomething($requiredArgument, $optionalArgument);         $this->assertSame($expected, $result,              'Expected result differs from actual result');     }
  24. 24. Example: testing payments <?php   namespace  MyappCommonPayment;       class  ProcessTest  extends  PHPUnit_Framework_TestCase   {          public  function  testPaymentIsProcessedCorrectly()          {                  $customer  =  new  Customer(/*  data  for  customer  */);                  $transaction  =  new  Transaction(/*  data  for  transaction  */);                  $process  =  new  Process('sale',  $customer,  $transaction);                  $process-­‐>pay();                      $this-­‐>assertTrue($process-­‐>paymentApproved());                  $this-­‐>assertEquals('PAY-­‐17S8410768582940NKEE66EQ',  $process-­‐ >getPaymentId());          }   }
  25. 25. We don’t live in a fairy tale! https://www.flickr.com/photos/bertknot/8175214909
  26. 26. Real code, real apps
  27. 27. github.com/Telaxus/EPESI
  28. 28. Running the project
  29. 29. Where are the TESTS?
  30. 30. Where are the TESTS?
  31. 31. Oh noes, no tests! https://www.flickr.com/photos/mjhagen/2973212926
  32. 32. Let’s get started https://www.flickr.com/photos/npobre/2601582256
  33. 33. How to get about it?
  34. 34. Setting up for testing <phpunit colors="true" stopOnError="true" stopOnFailure="true"> <testsuites> <testsuite name="EPESI admin tests"> <directory phpVersion="5.3.0">tests/admin</directory> </testsuite> <testsuite name="EPESI include tests"> <directory phpVersion="5.3.0">tests/include</directory> </testsuite> <testsuite name="EPESI modules testsuite"> <directory phpVersion="5.3.0">tests/modules</directory> </testsuite> </testsuites> <php> <const name="DEBUG_AUTOLOADS" value="1"/> <const name="CID" value="1234567890123456789"/> </php> <logging> <log type="coverage-html" target="build/coverage" charset="UTF-8"/> <log type="coverage-clover" target="build/logs/clover.xml"/> <log type="junit" target="build/logs/junit.xml"/> </logging> </phpunit>
  35. 35. ModuleManager • not_loaded_modules • loaded_modules • modules • modules_install • modules_common • root • processing • processed_modules • include_install • include_common • include_main • create_load_priority_array • check_dependencies • satisfy_dependencies • get_module_dir_path • get_module_file_name • list_modules • exists • register • unregister • is_installed • upgrade • downgrade • get_module_class_name • install • uninstall • get_processed_modules • get_load_priority_array • new_instance • get_instance • create_data_dir • remove_data_dir • get_data_dir • load_modules • create_common_cache • create_root • check_access • call_common_methods • check_common_methods • required_modules • reset_cron
  36. 36. ModuleManager::module_in stall /**  * Includes file with module installation class.  *  * Do not use directly.  *  * @param string $module_class_name module class name - underscore separated  */ public static final function include_install($module_class_name) {     if(isset(self::$modules_install[$module_class_name])) return true;     $path = self::get_module_dir_path($module_class_name);     $file = self::get_module_file_name($module_class_name);     $full_path = 'modules/' . $path . '/' . $file . 'Install.php';     if (!file_exists($full_path)) return false;     ob_start();     $ret = require_once($full_path);     ob_end_clean();     $x = $module_class_name.'Install';     if(!(class_exists($x, false)) ||  !array_key_exists('ModuleInstall',class_parents($x)))         trigger_error('Module '.$path.': Invalid install file',E_USER_ERROR);     self::$modules_install[$module_class_name] = new $x($module_class_name);     return true; }
  37. 37. Testing first condition <?php require_once 'include.php'; class ModuleManagerTest extends PHPUnit_Framework_TestCase {     protected function tearDown()     {         ModuleManager::$modules_install = array ();     }     public function testReturnImmediatelyWhenModuleAlreadyLoaded()     {         $module = 'Foo_Bar';         ModuleManager::$modules_install[$module] = 1;         $result = ModuleManager::include_install($module);         $this->assertTrue($result,             'Expecting that an already installed module returns true');         $this->assertCount(1, ModuleManager::$modules_install,             'Expecting to find 1 module ready for installation');     } }
  38. 38. Run test
  39. 39. Check coverage
  40. 40. Test for second condition public function testLoadingNonExistingModuleIsNotExecuted() {     $module = 'Foo_Bar';     $result = ModuleManager::include_install($module);     $this- >assertFalse($result, 'Expecting failure for loading Foo_Bar');     $this->assertEmpty(ModuleManager::$modules_install,         'Expecting to find no modules ready for installation'); }
  41. 41. Run tests
  42. 42. Check coverage
  43. 43. Test for third condition public function testNoInstallationOfModuleWithoutInstallationClass( ) {     $module = 'EssClient_IClient';     $result = ModuleManager::include_install($module);     $this- >assertFalse($result, 'Expecting failure for loading Foo_Bar');     $this->assertEmpty(ModuleManager::$modules_install,         'Expecting to find no modules ready for installation'); }
  44. 44. Run tests
  45. 45. Check code coverage
  46. 46. Non-executable code https://www.flickr.com/photos/dazjohnson/7720806824
  47. 47. Test for success public function testIncludeClassFileForLoadingModule() {     $module = 'Base_About';     $result = ModuleManager::include_install($module);     $this->assertTrue($result, 'Expected module to be loaded');     $this->assertCount(1, ModuleManager::$modules_install,         'Expecting to find 1 module ready for installation'); }
  48. 48. Run tests
  49. 49. Check code coverage
  50. 50. Look at the global coverage
  51. 51. Bridging gaps https://www.flickr.com/photos/hugo90/6980712643
  52. 52. Privates exposed http://www.slashgear.com/former-tsa-agent-admits-we-knew-full-body-scanners-didnt-work-31315288/
  53. 53. Dependency • __construct • get_module_name • get_version_min • get_version_max • is_satisfied_by • requires • requires_exact • requires_at_least • requires_range
  54. 54. A private constructor! <?php defined("_VALID_ACCESS") || die('Direct access forbidden'); /**  * This class provides dependency requirements  * @package epesi-base  * @subpackage module   */ class Dependency {     private $module_name;     private $version_min;     private $version_max;     private $compare_max;     private function __construct( $module_name, $version_min, $version_max, $version_max_is_ok = true) {         $this->module_name = $module_name;         $this->version_min = $version_min;         $this->version_max = $version_max;         $this->compare_max = $version_max_is_ok ? '<=' : '<';     }     /** ... */ }
  55. 55. Don’t touch my junk! https://www.flickr.com/photos/caseymultimedia/5412293730
  56. 56. House of Reflection https://www.flickr.com/photos/tabor-roeder/8250770115
  57. 57. Let’s do this… <?php require_once 'include.php'; class DependencyTest extends PHPUnit_Framework_TestCase {     public function testConstructorSetsProperSettings()     {         require_once 'include/module_dependency.php';         // We have a problem, the constructor is private!     } }
  58. 58. Let’s use the static $params = array (     'moduleName' => 'Foo_Bar',     'minVersion' => 0,     'maxVersion' => 1,     'maxOk' => true, ); // We use a static method for this test $dependency = Dependency::requires_range(     $params['moduleName'],     $params['minVersion'],     $params['maxVersion'],     $params['maxOk'] ); // We use reflection to see if properties are set correctly $reflectionClass = new ReflectionClass('Dependency');
  59. 59. Use the reflection to assert // Let's retrieve the private properties $moduleName = $reflectionClass->getProperty('module_name'); $moduleName->setAccessible(true); $minVersion = $reflectionClass->getProperty('version_min'); $minVersion->setAccessible(true); $maxVersion = $reflectionClass->getProperty('version_max'); $maxVersion->setAccessible(true); $maxOk = $reflectionClass->getProperty('compare_max'); $maxOk->setAccessible(true); // Let's assert $this->assertEquals($params['moduleName'], $moduleName->getValue($dependency),     'Expected value does not match the value set’); $this->assertEquals($params['minVersion'], $minVersion->getValue($dependency),     'Expected value does not match the value set’); $this->assertEquals($params['maxVersion'], $maxVersion->getValue($dependency),     'Expected value does not match the value set’); $this->assertEquals('<=', $maxOk->getValue($dependency),     'Expected value does not match the value set');
  60. 60. Run tests
  61. 61. Code Coverage
  62. 62. Yes, paradise exists https://www.flickr.com/photos/rnugraha/2003147365
  63. 63. Unit testing is not difficult!
  64. 64. Get started
  65. 65. PHP has all the tools
  66. 66. And there are more roads to Rome
  67. 67. You’re one-stop fix phpunit.de
  68. 68. Recommended reading
  69. 69. https://www.flickr.com/photos/lwr/13442542235
  70. 70. Contact us in it2PROFESSIONAL PHP SERVICES Michelangelo van Dam michelangelo@in2it.be www.in2it.be PHP Consulting - Training - QA
  71. 71. cfp.phpbenelux.eu Submit your proposals before October 14, 2015
  72. 72. Thank you Have a great conference http://www.flickr.com/photos/drewm/3191872515

×