Quality Assurance in PHP Projects - Presentation Transcript
Quality Assurance in PHP Projects
Sebastian Bergmann
CodeWorks 2009
Sebastian Bergmann
Co-Founder and
Principal Consultant
with thePHP.cc
Creator of PHPUnit
Involved in the PHP
project since 2000
When things go wrong in software projects,
the team has to work overtime and cancel vacations.
More often than not,
deadlines and quality goals are missed nevertheless.
You will ask yourself: ”Why did I not test this?”
Testing Web Applications
EndtoEnd Testing
Static Analysis of HTML does not work
Instead: Test web applications in the
browser
EndtoEnd Testing
Manual Testing
Not automated, slow and expensive
Complete application must be ready
before it can be tested
Tests need to be repeated
when the application changes
EndtoEnd Testing
Automated Testing
Test results are
automatically evaluated and
do not have to be interpreted by a
human tester
Tests are repeatable
with deterministic results
without additional costs
EndtoEnd Testing
Selenium
Selenium
Test web applications in a web browser
Selenium IDE
Extension for Firefox
Record, execute, edit, debug tests in the
browser
Show
Me
A
Demo!
EndtoEnd Testing
Selenium RC
Automated execution of Selenium tests
Tests can be specified in any language
One test can be executed on multiple
OS / browser combinations
EndtoEnd Testing
Selenium RC
EndtoEnd Testing
Problems
Complex test environments
Dependencies between tests
Isolation of test execution
Running the tests is slow,
thus incompatible with the agile approach
EndtoEnd Testing
Problems
End-to-End Testing is not enough
and/or does not scale
We want to test earlier
We want to be able to test an incomplete
application
We want a test environment that is less complex
We want to be able to run the tests faster
Does my code work?
Be confident in your code!
Do not be afraid of changing your code!
Does my code work?
Classic Approach
Write a test program
Run the test program
Manually verify the output
Delete the test program
Lets focus on a more modern approach.
Does my code work?
Agile Approach
Write a Unit Test
Executable specification
Automatic evaluation
Simple test environment
Instant feedback
Keep as regression test
Unit Tests improve the confidence in your code
as they detect problems as early as possible.
phpunit
De-facto standard for the unit testing
of PHP applications
Member of the xUnit family
Inspired by
JUnit
TestNG
JUnitour
JExample
…
10 frames
2 rolls to knock down the 10 pins
Score for a frame is the number of pins knocked down
Bonus for a spare (all 10 pins knocked down in two tries): next roll
Bonus for a strike (all 10 pins knocked down in one try): next two rolls
Extra rolls for spare or strike in the 10th frame
Show
Me
The
Code!
The Bowling Game Kata
The first test
<?php
require_once 'BowlingGame.php';
class BowlingGameTest extends PHPUnit_Framework_TestCase
{
}
The Bowling Game Kata
The first test
<?php
require_once 'BowlingGame.php';
class BowlingGameTest extends PHPUnit_Framework_TestCase
{
public function testScoreForGutterGameIs0()
{
}
}
The Bowling Game Kata
The first test
<?php
require_once 'BowlingGame.php';
class BowlingGameTest extends PHPUnit_Framework_TestCase
{
public function testScoreForGutterGameIs0()
{
$game = new BowlingGame;
for ($i = 0; $i < 20; $i++) {
$game->roll(0);
}
$this->assertEquals(0, $game->score());
}
}
The Bowling Game Kata
Generating a class skeleton based on a test case class
sb@ubuntu ~ % phpunit --skeleton-class BowlingGameTest
PHPUnit 3.4.0 by Sebastian Bergmann.
Wrote skeleton for "BowlingGame" to "BowlingGame.php".
<?php
class BowlingGame
{
/**
* @todo Implement roll().
*/
public function roll()
{
// Remove the following line when you implement this method.
throw new RuntimeException('Not yet implemented.');
}
/**
* @todo Implement score().
*/
public function score()
{
// Remove the following line when you implement this method.
throw new RuntimeException('Not yet implemented.');
}
}
?>
The Bowling Game Kata
Running the tests of a test case class
sb@ubuntu ~ % phpunit BowlingGameTest
PHPUnit 3.4.0 by Sebastian Bergmann.
E
Time: 0 seconds
There was 1 error:
1) BowlingGameTest::testScoreForGutterGameIs0
RuntimeException: Not yet implemented.
/home/sb/BowlingGame.php:10
/home/sb/BowlingGameTest.php:11
FAILURES!
Tests: 1, Assertions: 0, Errors: 1.
The Bowling Game Kata
The BowlingGame class
<?php
class BowlingGame
{
public function roll($pins)
{
}
public function score()
{
return 0;
}
}
The Bowling Game Kata
Running the tests of a test case class
sb@ubuntu ~ % phpunit BowlingGameTest
PHPUnit 3.4.0 by Sebastian Bergmann.
.
Time: 0 seconds
OK (1 test, 1 assertion)
The Bowling Game Kata
Running the tests of a test case class
sb@ubuntu ~ % phpunit --colors BowlingGameTest
PHPUnit 3.4.0 by Sebastian Bergmann.
.
Time: 0 seconds
OK (1 test, 1 assertion)
Interlude
TestDriven Development
Method of designing software,
not just a method of testing software
Tests drive the development
Tests written before code
No code without tests
Interlude
TestDriven Development
Test
What do we want score() to do?
How do we want to tell score() to do it?
How will we know when score() has done it?
Code
How does score() do it?
Interlude
Testivus Manifestivus
The best time to test
is when the code is fresh
Your code is like clay.
When it’s fresh, it’s soft and malleable.
As it ages, it becomes hard and brittle.
If you write tests when the code is fresh
and easy to change, testing will be easy,
and both the code and the tests will be strong.
This slide contains material by Alberto Savoia
Interlude
Testivus Manifestivus
Think of code and test as one
When writing the code, think of the test.
When writing the test, think of the code.
When you think of code and test as one,
testing is easy and code is beautiful.
This slide contains material by Alberto Savoia
The Bowling Game Kata
The second test
<?php
require_once 'BowlingGame.php';
class BowlingGameTest extends PHPUnit_Framework_TestCase
{
// ...
public function testScoreForAllOnesIs20()
{
$game = new BowlingGame;
for ($i = 0; $i < 20; $i++) {
$game->roll(1);
}
$this->assertEquals(20, $game->score());
}
}
The Bowling Game Kata
The second test
sb@ubuntu ~ % phpunit BowlingGameTest
PHPUnit 3.4.0 by Sebastian Bergmann.
.F
Time: 0 seconds
There was 1 failure:
1) BowlingGameTest::testScoreForAllOnesIs20
Failed asserting that <integer:0> matches expected value <integer:20>.
/home/sb/BowlingGameTest.php:25
FAILURES!
Tests: 2, Assertions: 2, Failures: 1.
The Bowling Game Kata
The second test
sb@ubuntu ~ % phpunit --colors BowlingGameTest
PHPUnit 3.4.0 by Sebastian Bergmann.
.F
Time: 0 seconds
There was 1 failure:
1) BowlingGameTest::testScoreForAllOnesIs20
Failed asserting that <integer:0> matches expected value <integer:20>.
/home/sb/BowlingGameTest.php:25
FAILURES!
Tests: 2, Assertions: 2, Failures: 1.
The Bowling Game Kata
Leveraging the setUp() template method
<?php
require_once 'BowlingGame.php';
class BowlingGameTest extends PHPUnit_Framework_TestCase
{
protected $game;
protected function setUp()
{
$this->game = new BowlingGame;
}
// ...
public function testScoreForAllOnesIs20()
{
for ($i = 0; $i < 20; $i++) {
$this->game->roll(1);
}
$this->assertEquals(20, $this->game->score());
}
// ...
}
The Bowling Game Kata
Introducing the rollMany() method
<?php
require_once 'BowlingGame.php';
class BowlingGameTest extends PHPUnit_Framework_TestCase
{
// ...
protected function rollMany($n, $pins)
{
for ($i = 0; $i < $n; $i++) {
$this->game->roll($pins);
}
}
public function testScoreForGutterGameIs0()
{
$this->rollMany(20, 0);
$this->assertEquals(0, $this->game->score());
}
public function testScoreForAllOnesIs20()
{
$this->rollMany(20, 1);
$this->assertEquals(20, $this->game->score());
}
}
The Bowling Game Kata
The third test
<?php
require_once 'BowlingGame.php';
class BowlingGameTest extends PHPUnit_Framework_TestCase
{
// ...
public function testScoreForOneSpareAnd3Is16()
{
$this->game->roll(5);
$this->game->roll(5);
$this->game->roll(3);
$this->rollMany(17, 0);
$this->assertEquals(16, $this->game->score());
}
}
The Bowling Game Kata
The fourth test
<?php
require_once 'BowlingGame.php';
class BowlingGameTest extends PHPUnit_Framework_TestCase
{
// ...
public function testScoreForOneStrikeAnd3And4Is24()
{
$this->game->roll(10);
$this->game->roll(3);
$this->game->roll(4);
$this->rollMany(17, 0);
$this->assertEquals(24, $this->game->score());
}
}
The Bowling Game Kata
The fifth test
<?php
require_once 'BowlingGame.php';
class BowlingGameTest extends PHPUnit_Framework_TestCase
{
// ...
public function testScoreForPerfectGameIs300()
{
$this->rollMany(12, 10);
$this->assertEquals(300, $this->game->score());
}
}
The Bowling Game Kata
Running the tests of a test case class
sb@ubuntu ~ % phpunit BowlingGameTest
PHPUnit 3.4.0 by Sebastian Bergmann.
.FFFF
Time: 0 seconds
There were 4 failures:
1) BowlingGameTest::testScoreForAllOnesIs20
Failed asserting that <integer:0> matches expected value <integer:20>.
/home/sb/BowlingGameTest.php:67
2) BowlingGameTest::testScoreForOneSpareAnd3Is16
Failed asserting that <integer:0> matches expected value <integer:16>.
/home/sb/BowlingGameTest.php:75
3) BowlingGameTest::testScoreForOneStrikeAnd3And4Is24
Failed asserting that <integer:0> matches expected value <integer:24>.
/home/sb/BowlingGameTest.php:84
4) BowlingGameTest::testScoreForPerfectGameIs300
Failed asserting that <integer:0> matches expected value <integer:300>.
/home/sb/BowlingGameTest.php:90
FAILURES!
Tests: 5, Assertions: 5, Failures: 4.
PHPUnit
Tests can serve as an executable specification.
The Bowling Game Kata
TestDox: Report test result as executable specification
sb@ubuntu ~ % phpunit --testdox BowlingGameTest
PHPUnit 3.4.0 by Sebastian Bergmann.
BowlingGame
[x] Score for gutter game is 0
[ ] Score for all ones is 20
[ ] Score for one spare and 3 is 16
[ ] Score for one strike and 3 and 4 is 24
[ ] Score for perfect game is 300
The Bowling Game Kata
The BowlingGame class
<?php
class BowlingGame
{
protected $rolls = array();
public function roll($pins)
{
$this->rolls[] = $pins;
}
// ...
}
The Bowling Game Kata
The BowlingGame class
<?php
class BowlingGame
{
// ...
protected function isSpare($frameIndex)
{
return $this->sumOfPinsInFrame($frameIndex) == 10;
}
protected function isStrike($frameIndex)
{
return $this->rolls[$frameIndex] == 10;
}
protected function sumOfPinsInFrame($frameIndex)
{
return $this->rolls[$frameIndex] +
$this->rolls[$frameIndex + 1];
}
}
The Bowling Game Kata
The BowlingGame class
<?php
class BowlingGame
{
// ...
protected function spareBonus($frameIndex)
{
return $this->rolls[$frameIndex + 2];
}
protected function strikeBonus($frameIndex)
{
return $this->rolls[$frameIndex + 1] +
$this->rolls[$frameIndex + 2];
}
}
The Bowling Game Kata
The BowlingGame class
<?php
class BowlingGame
{
// ...
public function score()
{
$score = 0;
$frameIndex = 0;
for ($frame = 0; $frame < 10; $frame++) {
if ($this->isStrike($frameIndex)) {
$score += 10 + $this->strikeBonus($frameIndex);
$frameIndex++;
}
else if ($this->isSpare($frameIndex)) {
$score += 10 + $this->spareBonus($frameIndex);
$frameIndex += 2;
}
else {
$score += $this->sumOfPinsInFrame($frameIndex);
$frameIndex += 2;
}
}
return $score;
}
}
The Bowling Game Kata
TestDox: Report test result as executable specification
sb@ubuntu ~ % phpunit --testdox BowlingGameTest
PHPUnit 3.4.0 by Sebastian Bergmann.
BowlingGame
[x] Score for gutter game is 0
[x] Score for all ones is 20
[x] Score for one spare and 3 is 16
[x] Score for one strike and 3 and 4 is 24
[x] Score for perfect game is 300
A unit test should run in less than 1 ms.
Software Testing Categorization
Small: Unit Tests
Check conditional logic in the code
A debugger should not be required in case of failure
Runs in less than 1 ms
Medium: Functional Tests
Check whether the interfaces between classes
abide by their contracts
Large: End-to-End Tests
Check for ”wiring bugs”
This slide contains material by Miško Hevery
The Bowling Game Kata
Creating a test case class skeleton for existing code
sb@ubuntu ~ % phpunit --skeleton-test BowlingGame
PHPUnit 3.4.0 by Sebastian Bergmann.
Wrote skeleton for "BowlingGameTest" to "BowlingGameTest.php".
sb@ubuntu ~ % phpunit --verbose BowlingGameTest
PHPUnit 3.4.0 by Sebastian Bergmann.
BowlingGameTest
II
Time: 0 seconds
There were 2 incomplete tests:
1) BowlingGameTest::testRoll
This test has not been implemented yet.
/home/sb/BowlingGameTest.php:46
2) BowlingGameTest::testScore
This test has not been implemented yet.
/home/sb/BowlingGameTest.php:56
OK, but incomplete or skipped tests!
Tests: 2, Assertions: 0, Incomplete: 2.
The Bowling Game Kata
Code Coverage
sb@ubuntu ~ % phpunit --coverage-html /tmp/report BowlingGameTest
PHPUnit 3.4.0 by Sebastian Bergmann.
.....
Time: 0 seconds
OK (5 tests, 5 assertions)
Generating report, this may take a moment.
Show
Me
A
Demo!
Test Doubles
How can we verify logic independently
when code it depends on is unusable?
How can we avoid slow tests?
We replace a component on which the SUT
depends with a “test-specific equivalent”.
Test Doubles
Terminology
Dummy
Not the real object
Fake
Usable for testing but
not for real job
Stub
Fake that returns canned
data
Spy
Stub that records called
methods, etc.
Mock
Spy with expectations
Test Doubles
Stubbing a method with PHPUnit
<?php
class StubTest extends PHPUnit_Framework_TestCase
{
public function testStub()
{
}
}
?>
Test Doubles
Stubbing a method with PHPUnit
<?php
class StubTest extends PHPUnit_Framework_TestCase
{
public function testStub()
{
$stub = $this->getMock('SomeClass');
}
}
?>
Test Doubles
Stubbing a method with PHPUnit: returnValue()
<?php
class StubTest extends PHPUnit_Framework_TestCase
{
public function testStub()
{
$stub = $this->getMock('SomeClass');
$stub->expects($this->any())
->method('doSomething')
->will($this->returnValue('foo'));
}
}
?>
Test Doubles
Stubbing a method with PHPUnit: returnValue()
<?php
class StubTest extends PHPUnit_Framework_TestCase
{
public function testStub()
{
$stub = $this->getMock('SomeClass');
$stub->expects($this->any())
->method('doSomething')
->will($this->returnValue('foo'));
// Calling $stub->doSomething() will now return
// 'foo'.
}
}
?>
Test Doubles
Stubbing a method with PHPUnit: returnArgument()
<?php
class StubTest extends PHPUnit_Framework_TestCase
{
public function testReturnArgumentStub()
{
$stub = $this->getMock(
'SomeClass', array('doSomething')
);
$stub->expects($this->any())
->method('doSomething')
->will($this->returnArgument(0));
// $stub->doSomething('foo') returns 'foo'
// $stub->doSomething('bar') returns 'bar'
}
}
Test Doubles
Stubbing a method with PHPUnit: returnCallback()
<?php
class StubTest extends PHPUnit_Framework_TestCase
{
public function testReturnCallbackStub()
{
$stub = $this->getMock(
'SomeClass', array('doSomething')
);
$stub->expects($this->any())
->method('doSomething')
->will($this->returnCallback('callback'));
// $stub->doSomething() returns callback(...)
}
}
function callback() {
$args = func_get_args();
// ...
}
Test Doubles
Stubbing a method with PHPUnit: throwException()
<?php
class StubTest extends PHPUnit_Framework_TestCase
{
public function testThrowExceptionStub()
{
$stub = $this->getMock(
'SomeClass', array('doSomething')
);
$stub->expects($this->any())
->method('doSomething')
->will($this->throwException(new Exception));
// $stub->doSomething() throws Exception
}
}
Test Doubles
Mocking a method with PHPUnit
<?php
class SubjectTest extends PHPUnit_Framework_TestCase {
public function testObserversAreUpdated() {
}
}
?>
Test Doubles
Mocking a method with PHPUnit
<?php
class SubjectTest extends PHPUnit_Framework_TestCase {
public function testObserversAreUpdated() {
$observer = $this->getMock(
'Observer', array('update')
);
}
}
?>
Test Doubles
Mocking a method with PHPUnit
<?php
class SubjectTest extends PHPUnit_Framework_TestCase {
public function testObserversAreUpdated() {
$observer = $this->getMock(
'Observer', array('update')
);
$observer->expects($this->once())
->method('update')
->with($this->equalTo('something'));
}
}
?>
Test Doubles
Mocking a method with PHPUnit
<?php
class SubjectTest extends PHPUnit_Framework_TestCase {
public function testObserversAreUpdated() {
$observer = $this->getMock(
'Observer', array('update')
);
$observer->expects($this->once())
->method('update')
->with($this->equalTo('something'));
$subject = new Subject;
$subject->attach($observer);
$subject->doSomething();
}
}
?>
Test Doubles
Mocking the filesystem with vfsStream
<?php
class Example {
protected $id;
protected $directory;
public function __construct($id) {
$this->id = $id;
}
public function setDirectory($directory) {
$this->directory = $directory . DIRECTORY_SEPARATOR . $this->id;
if (!file_exists($this->directory)) {
mkdir($this->directory, 0700, TRUE);
}
}
}
?>
Test Doubles
Mocking the filesystem with vfsStream
<?php
require_once 'vfsStream/vfsStream.php';
require_once 'Example.php';
class ExampleTest extends PHPUnit_Framework_TestCase {
public function setUp() {
vfsStreamWrapper::register();
vfsStreamWrapper::setRoot(new vfsStreamDirectory('exampleDir'));
}
public function testDirectoryIsCreated() {
$example = new Example('id');
$this->assertFalse(vfsStreamWrapper::getRoot()->hasChild('id'));
$example->setDirectory(vfsStream::url('exampleDir'));
$this->assertTrue(vfsStreamWrapper::getRoot()->hasChild('id'));
}
}
?>
PHPUnit
Data Provider
<?php
class DataTest extends PHPUnit_Framework_TestCase
{
/**
* @dataProvider providerMethod
*/
public function testAdd($a, $b, $c)
{
$this->assertEquals($c, $a + $b);
}
public function providerMethod()
{
return array(
array(0, 0, 0),
array(0, 1, 1),
array(1, 1, 3),
array(1, 0, 1)
);
}
}
PHPUnit
Data Provider
sb@ubuntu ~ % phpunit DataTest
PHPUnit 3.4.0 by Sebastian Bergmann.
..F.
Time: 0 seconds
There was 1 failure:
1) DataTest::testAdd with data (1, 1, 3)
Failed asserting that <integer:2> matches expected value <integer:3>.
/home/sb/DataTest.php:19
FAILURES!
Tests: 4, Assertions: 4, Failures: 1.
PHPUnit
Test Dependencies
<?php
class StackTest extends PHPUnit_Framework_TestCase {
public function testEmpty() {
$stack = array();
$this->assertTrue(empty($stack));
return $stack;
}
}
PHPUnit
Test Dependencies
<?php
class StackTest extends PHPUnit_Framework_TestCase {
public function testEmpty() {
$stack = array();
$this->assertTrue(empty($stack));
return $stack;
}
/**
* @depends testEmpty
*/
public function testPush(array $stack) {
array_push($stack, 'foo');
$this->assertFalse(empty($stack));
$this->assertEquals('foo', $stack[count($stack) - 1]);
return $stack;
}
}
PHPUnit
Test Dependencies
<?php
class StackTest extends PHPUnit_Framework_TestCase {
public function testEmpty() {
$stack = array();
$this->assertTrue(empty($stack));
return $stack;
}
/**
* @depends testEmpty
*/
public function testPush(array $stack) {
array_push($stack, 'foo');
$this->assertFalse(empty($stack));
$this->assertEquals('foo', $stack[count($stack) - 1]);
return $stack;
}
/**
* @depends testPush
*/
public function testPop(array $stack) {
$this->assertEquals('foo', array_pop($stack));
$this->assertTrue(empty($stack));
}
}
PHPUnit
Test Dependencies
<?php
class DependencyFailureTest extends PHPUnit_Framework_TestCase
{
public function testOne()
{
$this->assertTrue(FALSE);
}
/**
* @depends testOne
*/
public function testTwo()
{
}
}
PHPUnit
Test Dependencies
sb@ubuntu ~ % phpunit --verbose DependencyFailureTest
PHPUnit 3.4.0 by Sebastian Bergmann.
DependencyFailureTest
FS
Time: 0 seconds
There was 1 failure:
1) DependencyFailureTest::testOne
Failed asserting that <boolean:false> is true.
/home/sb/DependencyFailureTest.php:6
There was 1 skipped test:
1) DependencyFailureTest::testTwo
This test depends on "DependencyFailureTest::testOne" to pass.
FAILURES!
Tests: 2, Assertions: 1, Failures: 1, Skipped: 1.
PHPUnit
DatabaseTestCase
Used to test database-driven projects
Puts the database into a known state
between test runs
Avoids problems with one test corrupting the
database for other tests
Ability to export and import data to and from
CSV, XML and YAML datasets
PHPUnit
DatabaseTestCase
When testing PHP code that uses PDO
to connect to a database, it makes sense
to keep your SQL compatible with SQLite
No server ⇒ No inter-process communication
In-Memory Databases ⇒ No Disk I/O
User System CPU Total
PDO / MySQL 3.95s 0.87s 40% 12.046s
PDO / SQLite (file) 5.01s 1.54s 63% 10.359s
PDO / SQLite (memory) 3.16s 0.68s 99% 3.849s
PHPUnit
Integration with Selenium RC
<?php
require_once 'PHPUnit/Extensions/SeleniumTestCase.php';
class WebTest extends PHPUnit_Extensions_SeleniumTestCase
{
}
?>
PHPUnit
Integration with Selenium RC
<?php
require_once 'PHPUnit/Extensions/SeleniumTestCase.php';
class WebTest extends PHPUnit_Extensions_SeleniumTestCase
{
protected function setUp()
{
$this->setBrowser('*firefox');
$this->setBrowserUrl('http://www.example.com/');
$this->setSleep(10);
}
}
?>
PHPUnit
Integration with Selenium RC
<?php
require_once 'PHPUnit/Extensions/SeleniumTestCase.php';
class WebTest extends PHPUnit_Extensions_SeleniumTestCase
{
protected function setUp()
{
$this->setBrowser('*firefox');
$this->setBrowserUrl('http://www.example.com/');
$this->setSleep(10);
}
public function testTitle()
{
$this->open('http://www.example.com/');
$this->assertTitle('Example Web Page');
}
}
?>
Show
Me
A
Demo!
Software Metrics
Measuring Software and Software Quality
„You cannot control what
you cannot measure.“
– Tom DeMarco
Software Metrics
Code Coverage
Which statements, branches, and paths
are executed when the tests run?
Statement Coverage
Branch Coverage
Path Coverage
100% Code Coverage is a required,
but not a sufficient criteria for test
completeness
Software Metrics
Code Coverage
sb@thinkpad ~ % phpunit --coverage-html /tmp/report BankAccountTest
PHPUnit 3.4.0 by Sebastian Bergmann.
.....
Time: 0 seconds
OK (5 tests, 5 assertions)
Generating code coverage report, this may take a moment.
Software Metrics
Code Analysis
Software Metrics
Lines of Code
Text-based metric for code size
Various definitions
Lines of Code (LOC)
Comment Lines of Code (CLOC)
Non-Comment Lines of Code (NCLOC)
Executable Lines of Code (ELOC)
Ratios can be interesting
CLOC / (E)LOC
Software Metrics
Lines of Code
sb@thinkpad ~ % phploc --count-tests /usr/local/src/ezcomponents/trunk/Workflow
phploc 1.3.0 by Sebastian Bergmann.
Directories: 13
Files: 100
Lines of Code (LOC): 14065
Cyclomatic Complexity / Lines of Code: 0.09
Executable Lines of Code (ELOC): 5349
Comment Lines of Code (CLOC): 5213
Non-Comment Lines of Code (NCLOC): 8852
Interfaces: 6
Classes: 79
Abstract Classes: 12
Concrete Classes: 67
Lines of Code / Number of Classes: 178
Methods: 368
Non-Static Methods: 335
Static Methods: 33
Lines of Code / Number of Methods: 38
Cyclomatic Complexity / Number of Methods: 2.30
Functions: 0
Constants: 0
Class constants: 10
Test classes: 10
Test methods: 199
Software Metrics
Code Duplication
Two sequences of code are duplicate
when they are
textually identical
token for token identical
functionally identical
Problems
Duplicate code contradicts code reuse
Co-Evolution of clones hinders maintenance
Software Metrics
Code Duplication
sb@thinkpad ~ % phpcpd /usr/local/src/phpunit/trunk/PHPUnit
phpcpd 1.1.0 by Sebastian Bergmann.
Found 4 exact clones with 131 duplicated lines in 7 files:
- Extensions/Database/DataSet/AbstractTable.php:156-190
Extensions/Database/DataSet/ReplacementTable.php:172-206
- Samples/BankAccountDB/BankAccountDBTest.php:84-128
Samples/BankAccountDB/BankAccountDBTestMySQL.php:84-128
- Tests/Extensions/Database/DataSet/XmlDataSetsTest.php:71-98
Tests/Extensions/Database/DataSet/YamlDataSetTest.php:70-97
- Tests/Extensions/Database/DataSet/XmlDataSetsTest.php:71-97
Tests/Extensions/Database/DataSet/CsvDataSetTest.php:70-96
0.21% duplicated lines out of 61720 total lines of code.
Software Metrics
Code Complexity
Cyclomatic Complexity
Counts the number of branching points
if, for, foreach, while, case, catch, &&, ||,
ternary operator (?:)
NPath Complexity
Counts the number execution paths
Interpretation
Higher complexity leads to more errors
Higher complexity makes testing harder
Software Metrics
pdepend
Static Analysis of PHP Code
Software Metrics
Software Visualization
Helps to identify parts of an application that
should be refactored
Software Metrics
phpmd
Static Analysis of PHP Code
Based on pdepend
Reports violations of rules that operate on raw
software metrics data
Software Metrics
phpcs
Static Analysis of PHP Code
Based on ext/tokenizer
”Sniffs”
Coding Standard
Software Metrics
Bug Patterns
Performance Patterns
...
Software Metrics
bytekit-cli
Static Analysis of PHP Code
Based on ext/bytekit
Disassembles PHP bytecode
Visualizes PHP bytecode
Scans PHP bytecode
… for disallowed opcode sequences
… for direct output of variables
…
Software Metrics
bytekit-cli
1 <?php
2 if (TRUE) {
3 print '*';
4 }
5 ?>
sb@thinkpad ~ % bytekit if.php
bytekit-cli 1.0.0 by Sebastian Bergmann.
Filename: /home/sb/if.php
Function: main
Number of oplines: 8
line # opcode result operands
-----------------------------------------------------------------------------
2 0 EXT_STMT
1 JMPZ true, ->6
3 2 EXT_STMT
3 PRINT ~0 '*'
4 FREE ~0
4 5 JMP ->6
6 6 EXT_STMT
7 RETURN 1
Build Automation
Apache Ant
Java-based build tool
Kind of like make, without make's
wrinkles
Build files are XML-based, calling out a
target tree where various tasks get
executed
Build Automation
Apache Ant
sb@ubuntu Money % ant
Buildfile: build.xml
clean:
[delete] Deleting directory /home/sb/Money/build
prepare:
[mkdir] Created dir: /home/sb/Money/build/logs
phpcs:
phpmd:
phpcpd:
[exec] phpcpd 1.1.0 by Sebastian Bergmann.
[exec]
[exec] 0.00% duplicated lines out of 722 total lines of code.
pdepend:
[exec] PHP_Depend 0.9.4 by Manuel Pichler
[exec]
[exec] Executing Dependency-Analyzer:
[exec] 16
[exec]
[exec] Generating pdepend log files, this may take a moment.
phpunit:
[exec] PHPUnit 3.4.0 by Sebastian Bergmann.
[exec]
[exec] ......................
[exec]
[exec] Time: 0 seconds
[exec]
[exec] OK (22 tests, 34 assertions)
[exec]
[exec] Writing code coverage data to XML file, this may take a moment.
build:
BUILD SUCCESSFUL
Total time: 4 seconds
Continuous Integration
Feel the pulse of your project!
Continuous Integration
Software development practice where members
of a team integrate their work frequently
Usually each person integrates at least daily,
leading to multiple integrations per day
Each integration is verified by an automated
build (including test) to detect integration
errors as quickly as possible
Leads to significantly reduced integration
problems and allows a team to develop
cohesive software more rapidly
Continuous Integration
CruiseControl
Framework for a continuous build process
Includes, but is not limited to, plugins for
email notification, Apache Ant, Phing, and
various source control tools
A web interface is provided to view the
details of the current and previous builds
Continuous Integration
phpUnderControl
Customization of CruiseControl that caters to
the needs of PHP projects
PHPUnit
PHPDocumentor
PHP_CodeSniffer
(PHP_Depend)
(phpmd)
(phpcpd)
The End
Thank you for your interest!
These slides will be posted on
http://slideshare.net/sebastian_bergmann
I am co-authoring a book on
Quality Assurance in PHP Projects
http://phpqabook.com/
License
This presentation material is published under the Attribution-Share Alike 3.0 Unported
license.
You are free:
✔ to Share – to copy, distribute and transmit the work.
✔ to Remix – to adapt the work.
Under the following conditions:
● Attribution. You must attribute the work in the manner specified by the author or
licensor (but not in any way that suggests that they endorse you or your use of the
work).
● Share Alike. If you alter, transform, or build upon this work, you may distribute the
resulting work only under the same, similar or a compatible license.
For any reuse or distribution, you must make clear to others the license terms of this
work.
Any of the above conditions can be waived if you get permission from the copyright
holder.
Nothing in this license impairs or restricts the author's moral rights.
When things go wrong in software projects, the team more
When things go wrong in software projects, the team has to work overtime and cancel vacations. More often than not, deadlines and quality goals are missed nevertheless. Because software usually lives longer than originally planned, the real problems crop up when changes and extensions become necessary later on. In this workshop, Sebastian Bergmann, creator of PHPUnit, imparts comprehensive knowledge and experience about testing and quality assurance in PHP projects. The audience will learn about using PHPUnit for unit testing the business logic components and Selenium for end-to-end testing of modern web applications. But testing is only one aspect of controlling the quality of a software project. This is why the measuring of software quality by means of software metrics as well as establishing successful development processes and methods such as continuous integration are also discussed in this workshop. less
0 comments
Post a comment