Quality Assurance in PHP Projects

Loading...

Flash Player 9 (or above) is needed to view presentations.
We have detected that you do not have it on your computer. To install it, go here.

0 comments

Post a comment

    Post a comment
    Embed Video
    Edit your comment Cancel

    10 Favorites & 1 Group

    Quality Assurance in PHP Projects - Presentation Transcript

    1. Quality Assurance in PHP Projects Sebastian Bergmann CodeWorks 2009
    2. Sebastian Bergmann  Co-Founder and Principal Consultant with thePHP.cc  Creator of PHPUnit  Involved in the PHP project since 2000
    3. When things go wrong in software projects, the team has to work overtime and cancel vacations.
    4. More often than not, deadlines and quality goals are missed nevertheless.
    5. You will ask yourself: ”Why did I not test this?”
    6. Testing Web Applications End­to­End Testing  Static Analysis of HTML does not work  Instead: Test web applications in the browser
    7. End­to­End 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
    8. End­to­End 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
    9. End­to­End Testing Selenium  Selenium  Test web applications in a web browser  Selenium IDE  Extension for Firefox  Record, execute, edit, debug tests in the browser
    10. Show Me A Demo!
    11. End­to­End 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
    12. End­to­End Testing Selenium RC
    13. End­to­End Testing Problems  Complex test environments  Dependencies between tests  Isolation of test execution  Running the tests is slow, thus incompatible with the agile approach
    14. End­to­End 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
    15. Does my code work?  Be confident in your code!  Do not be afraid of changing your code!
    16. Does my code work? Classic Approach  Write a test program  Run the test program  Manually verify the output  Delete the test program
    17. Lets focus on a more modern approach.
    18. Does my code work? Agile Approach Write a Unit Test  Executable specification  Automatic evaluation  Simple test environment  Instant feedback  Keep as regression test
    19. Unit Tests improve the confidence in your code as they detect problems as early as possible.
    20. phpunit  De-facto standard for the unit testing of PHP applications  Member of the xUnit family  Inspired by  JUnit  TestNG  JUnitour  JExample  …
    21. PHPUnit Installation sb@ubuntu ~ % pear channel-discover pear.phpunit.de Adding Channel "pear.phpunit.de" succeeded Discovery of channel "pear.phpunit.de" succeeded sb@ubuntu ~ % pear install phpunit/phpunit downloading PHPUnit-3.4.0.tgz ... Starting to download PHPUnit-3.4.0.tgz (264,345 bytes) ...................................done: 264,345 bytes install ok: channel://pear.phpunit.de/PHPUnit-3.4.0
    22.  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
    23. Show Me The Code!
    24. The Bowling Game Kata The first test <?php require_once 'BowlingGame.php'; class BowlingGameTest extends PHPUnit_Framework_TestCase { }
    25. The Bowling Game Kata The first test <?php require_once 'BowlingGame.php'; class BowlingGameTest extends PHPUnit_Framework_TestCase { public function testScoreForGutterGameIs0() { } }
    26. 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()); } }
    27. 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.'); } } ?>
    28. 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.
    29. The Bowling Game Kata The BowlingGame class <?php class BowlingGame { public function roll($pins) { } public function score() { return 0; } }
    30. 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)
    31. 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)
    32. Interlude Test­Driven Development  Method of designing software, not just a method of testing software  Tests drive the development  Tests written before code  No code without tests
    33. Interlude Test­Driven 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?
    34. 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
    35. 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
    36. 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()); } }
    37. 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.
    38. 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.
    39. 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()); } // ... }
    40. 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()); } }
    41. 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()); } }
    42. 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()); } }
    43. 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()); } }
    44. 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.
    45. PHPUnit Tests can serve as an executable specification.
    46. 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
    47. The Bowling Game Kata The BowlingGame class <?php class BowlingGame { protected $rolls = array(); public function roll($pins) { $this->rolls[] = $pins; } // ... }
    48. 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]; } }
    49. 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]; } }
    50. 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; } }
    51. 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
    52. A unit test should run in less than 1 ms.
    53. 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
    54. 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.
    55. 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.
    56. Show Me A Demo!
    57. 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”.
    58. 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
    59. Test Doubles Stubbing a method with PHPUnit <?php class StubTest extends PHPUnit_Framework_TestCase { public function testStub() { } } ?>
    60. Test Doubles Stubbing a method with PHPUnit <?php class StubTest extends PHPUnit_Framework_TestCase { public function testStub() { $stub = $this->getMock('SomeClass'); } } ?>
    61. 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')); } } ?>
    62. 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'. } } ?>
    63. 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' } }
    64. 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(); // ... }
    65. 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 } }
    66. Test Doubles Mocking a method with PHPUnit <?php class SubjectTest extends PHPUnit_Framework_TestCase { public function testObserversAreUpdated() { } } ?>
    67. Test Doubles Mocking a method with PHPUnit <?php class SubjectTest extends PHPUnit_Framework_TestCase { public function testObserversAreUpdated() { $observer = $this->getMock( 'Observer', array('update') ); } } ?>
    68. 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')); } } ?>
    69. 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(); } } ?>
    70. 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); } } } ?>
    71. 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')); } } ?>
    72. 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) ); } }
    73. 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.
    74. PHPUnit Test Dependencies <?php class StackTest extends PHPUnit_Framework_TestCase { public function testEmpty() { $stack = array(); $this->assertTrue(empty($stack)); return $stack; } }
    75. 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; } }
    76. 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)); } }
    77. PHPUnit Test Dependencies <?php class DependencyFailureTest extends PHPUnit_Framework_TestCase { public function testOne() { $this->assertTrue(FALSE); } /** * @depends testOne */ public function testTwo() { } }
    78. 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.
    79. 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
    80. PHPUnit DatabaseTestCase <?php require_once 'PHPUnit/Extensions/Database/TestCase.php'; class BankAccountDBTest extends PHPUnit_Extensions_Database_TestCase { }
    81. PHPUnit DatabaseTestCase <?php require_once 'PHPUnit/Extensions/Database/TestCase.php'; class BankAccountDBTest extends PHPUnit_Extensions_Database_TestCase { protected $pdo; public function __construct() { $this->pdo = PHPUnit_Util_PDO::factory( 'mysql://test@localhost/test' ); BankAccount::createTable($this->pdo); } }
    82. PHPUnit DatabaseTestCase <?php require_once 'PHPUnit/Extensions/Database/TestCase.php'; class BankAccountDBTest extends PHPUnit_Extensions_Database_TestCase { protected $pdo; public function __construct() { $this->pdo = PHPUnit_Util_PDO::factory( 'mysql://test@localhost/test' ); BankAccount::createTable($this->pdo); } protected function getConnection() { return $this->createDefaultDBConnection($this->pdo, 'mysql'); } }
    83. PHPUnit DatabaseTestCase <?php require_once 'PHPUnit/Extensions/Database/TestCase.php'; class BankAccountDBTest extends PHPUnit_Extensions_Database_TestCase { protected $pdo; public function __construct() { $this->pdo = PHPUnit_Util_PDO::factory( 'mysql://test@localhost/test' ); BankAccount::createTable($this->pdo); } protected function getConnection() { return $this->createDefaultDBConnection($this->pdo, 'mysql'); } protected function getDataSet() { return $this->createFlatXMLDataSet('/path/to/seed.xml'); } }
    84. PHPUnit DatabaseTestCase <dataset> <account account_number="15934903649620486" balance="100.00" /> <account account_number="15936487230215067" balance="1216.00" /> <account account_number="12348612357236185" balance="89.00" /> <account account_number="15936487230215067" balance="1216.00" /> </dataset>
    85. PHPUnit DatabaseTestCase <?php require_once 'PHPUnit/Extensions/Database/TestCase.php'; class BankAccountDBTest extends PHPUnit_Extensions_Database_TestCase { // ... public function testNewAccount() { } }
    86. PHPUnit DatabaseTestCase <?php require_once 'PHPUnit/Extensions/Database/TestCase.php'; class BankAccountDBTest extends PHPUnit_Extensions_Database_TestCase { // ... public function testNewAccount() { $ba = new BankAccountDB('12345678912345678', $this->pdo); } }
    87. PHPUnit DatabaseTestCase <?php require_once 'PHPUnit/Extensions/Database/TestCase.php'; class BankAccountDBTest extends PHPUnit_Extensions_Database_TestCase { // ... public function testNewAccount() { $ba = new BankAccountDB('12345678912345678', $this->pdo); $set = $this->createFlatXMLDataSet( '/path/to/after-new-account.xml' ); } }
    88. PHPUnit DatabaseTestCase <?php require_once 'PHPUnit/Extensions/Database/TestCase.php'; class BankAccountDBTest extends PHPUnit_Extensions_Database_TestCase { // ... public function testNewAccount() { $ba = new BankAccountDB('12345678912345678', $this->pdo); $set = $this->createFlatXMLDataSet( '/path/to/after-new-account.xml' ); $this->assertTablesEqual( $set->getTable('account'), $this->getConnection() ->createDataSet() ->getTable('account') ); } }
    89. PHPUnit DatabaseTestCase <dataset> <account account_number="15934903649620486" balance="100.00" /> <account account_number="15936487230215067" balance="1216.00" /> <account account_number="12348612357236185" balance="89.00" /> <account account_number="15936487230215067" balance="1216.00" /> <account account_number="12345678912345678" balance="0.00" /> </dataset>
    90. 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
    91. PHPUnit Integration with Selenium RC <?php require_once 'PHPUnit/Extensions/SeleniumTestCase.php'; class WebTest extends PHPUnit_Extensions_SeleniumTestCase { } ?>
    92. 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); } } ?>
    93. 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'); } } ?>
    94. Show Me A Demo!
    95. Software Metrics Measuring Software and Software Quality „You cannot control what you cannot measure.“ – Tom DeMarco
    96. 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
    97. 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.
    98. Software Metrics Code Analysis
    99. 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
    100. 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
    101. 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
    102. 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.
    103. 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
    104. Software Metrics pdepend  Static Analysis of PHP Code  Software Metrics  Software Visualization  Helps to identify parts of an application that should be refactored
    105. Software Metrics phpmd  Static Analysis of PHP Code  Based on pdepend  Reports violations of rules that operate on raw software metrics data
    106. Software Metrics phpcs  Static Analysis of PHP Code  Based on ext/tokenizer  ”Sniffs”  Coding Standard  Software Metrics  Bug Patterns  Performance Patterns  ...
    107. 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  …
    108. 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
    109. Software Metrics bytekit-cli 1 <?php 2 if (TRUE) { 3 print '*'; 4 } 5 ?> sb@thinkpad ~ % bytekit --graph /tmp --format svg if.php
    110. Software Metrics bytekit-cli sb@thinkpad ~ % bytekit --rule DisallowedOpcodes:EVAL /usr/local/src/phpunit/trunk/PHPUnit bytekit-cli 1.0.0 by Sebastian Bergmann. - Disallowed opcode "EVAL" in /usr/local/src/phpunit/trunk/PHPUnit/Framework/TestCase.php:1160 - Disallowed opcode "EVAL" in /usr/local/src/phpunit/trunk/PHPUnit/Framework/TestCase.php:1061 - Disallowed opcode "EVAL" in /usr/local/src/phpunit/trunk/PHPUnit/Extensions/PhptTestCase.php:223 - Disallowed opcode "EVAL" in /usr/local/src/phpunit/trunk/PHPUnit/TextUI/Command.php:169
    111. QA Tools for PHP Overview
    112. Build Automation Automate {error­prone|recurring|tedious} tasks
    113. 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
    114. Build Automation Apache Ant <project name="Money" default="build"> <target name="clean"> <delete dir="${basedir}/build"/> </target> <target name="prepare"> <mkdir dir="${basedir}/build/logs"/> </target> <target name="phpcs"> <exec dir="${basedir}" executable="phpcs" output="${basedir}/build/logs/checkstyle.xml" failonerror="false"> <arg line="--report=checkstyle ."/> </exec> </target> <target name="phpmd"> <exec dir="${basedir}" executable="phpmd" failonerror="false"> <arg line=". xml codesize --reportfile ${basedir}/build/logs/pmd.xml"/> </exec> </target>
    115. Build Automation Apache Ant <target name="phpcpd"> <exec dir="${basedir}" executable="phpcpd" failonerror="false"> <arg line="--log-pmd=${basedir}/build/logs/pmd-cpd.xml ."/> </exec> </target> <target name="pdepend"> <exec dir="${basedir}" executable="pdepend" failonerror="false"> <arg line="--jdepend-xml=${basedir}/build/logs/jdepend.xml ."/> </exec> </target> <target name="phpunit"> <exec dir="${basedir}" executable="phpunit" failonerror="true"> <arg line="--log-xml ${basedir}/build/logs/junit.xml --coverage-clover ${basedir}/build/logs/clover.xml MoneyTest"/> </exec> </target> <target name="build" depends="clean,prepare,phpcs,phpmd,phpcpd,pdepend,phpunit"/> </project>
    116. 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
    117. Continuous Integration Feel the pulse of your project!
    118. 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
    119. 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
    120. Continuous Integration phpUnderControl Customization of CruiseControl that caters to the needs of PHP projects  PHPUnit  PHPDocumentor  PHP_CodeSniffer  (PHP_Depend)  (phpmd)  (phpcpd)
    121. 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/
    122. 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.

    + Sebastian BergmannSebastian Bergmann, 1 month ago

    custom

    1710 views, 10 favs, 0 embeds more stats

    When things go wrong in software projects, the team more

    More info about this document

    CC Attribution-ShareAlike LicenseCC Attribution-ShareAlike License

    Go to text version

    • Total Views 1710
      • 1710 on SlideShare
      • 0 from embeds
    • Comments 0
    • Favorites 10
    • Downloads 125
    Most viewed embeds

    more

    All embeds

    less

    Flagged as inappropriate Flag as inappropriate
    Flag as inappropriate

    Select your reason for flagging this presentation as inappropriate. If needed, use the feedback form to let us know more details.

    Cancel
    File a copyright complaint
    Having problems? Go to our helpdesk?

    Categories

    Groups / Events