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.

1 comments

Comments 1 - 1 of 1 previous next Post a comment

  • + afilina Anna Filina 4 months ago
    Very nice. I like the picture on slide 8. I hope to see more on bytekit-cli soon.
Post a comment
Embed Video
Edit your comment Cancel

2 Favorites & 1 Group

Quality Assurance in PHP Projects - Presentation Transcript

  1. QA in PHP Projects Sebastian Bergmann http://thePHP.cc July 21st 2009
  2. Who I am  Sebastian Bergmann  Involved in the PHP project since 2000  Creator of PHPUnit  Co-Founder and Principal Consultant with thePHP.cc
  3. What is great software? The customer­friendly programmer says ... ”Great software always does what the customer wants it to. So even if customers think of new ways to use the software, it doesn't break or give them unexpected results.” This slide contains material from ”Head First Object-Oriented Analysis & Design”, O'Reilly, 2006
  4. What is great software? The object­oriented programmer says ... ”Great software is code that is object- oriented. So there's not a bunch of duplicated code, and each object pretty much controls its own behaviour. It's also easy to extend because your design is really solid and flexible.” This slide contains material from ”Head First Object-Oriented Analysis & Design”, O'Reilly, 2006
  5. What is great software? The design­guru programmer says ... ”Great software is when you use tried- and-true design patterns and principles. You've kept your objects loosely coupled, and your code open for extension but closed for modification. That also helps make the code more reusable, so you don't have to rework everything to use parts of your application over and over again.” This slide contains material from ”Head First Object-Oriented Analysis & Design”, O'Reilly, 2006
  6. Aspects of Software Quality Good Code  Security  Few Branches  Robustness  Few Dependencies  Stability  Locality of Change  Performance  Small Blocks  Scalability  Self-Explanatory  Testable  Usability
  7. When things go wrong ...
  8. Why did I not test this?
  9. Testing Web Applications End­to­End Testing  Static Analysis of HTML does not work  Instead: Test web applications in the browser
  10. 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
  11. 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
  12. 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
  13. Show Me A Demo!
  14. 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
  15. End­to­End Testing Selenium RC
  16. 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
  17. 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
  18.  Does my code work?  Be confident in your code!  Do not be afraid of changing your code!
  19. Does my code work? Classic Approach  Write a test program  Run the test program  Manually verify the output  Delete the test program
  20. Does my code work? Agile Approach Write a unit test  Automatic evaluation of the test result  Instant feedback  Simple test environment  Allows for the earliest possible testing of a code unit  Executable specification  Is not deleted but kept as regression test
  21. Unit Tests improve the confidence in your code as they detect problems as early as possible
  22. 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.3.0.tgz ... Starting to download PHPUnit-3.3.0.tgz (264,345 bytes) ...................................done: 264,345 bytes install ok: channel://pear.phpunit.de/PHPUnit-3.3.0
  23.  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
  24. Show Me The Code!
  25. The Bowling Game Kata The first test <?php require_once 'BowlingGame.php'; class BowlingGameTest extends PHPUnit_Framework_TestCase { }
  26. The Bowling Game Kata The first test <?php require_once 'BowlingGame.php'; class BowlingGameTest extends PHPUnit_Framework_TestCase { public function testScoreForGutterGameIs0() { } }
  27. 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()); } }
  28. 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.'); } } ?>
  29. 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) testScoreForGutterGameIs0(BowlingGameTest) RuntimeException: Not yet implemented. /home/sb/BowlingGame.php:10 /home/sb/BowlingGameTest.php:11 FAILURES! Tests: 1, Assertions: 0, Errors: 1.
  30. The Bowling Game Kata The BowlingGame class <?php class BowlingGame { public function roll($pins) { } public function score() { return 0; } }
  31. 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)
  32. 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)
  33. 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
  34. 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?
  35. 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
  36. 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
  37. 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()); } }
  38. 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) testScoreForAllOnesIs20(BowlingGameTest) 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 The second test sb@ubuntu ~ % phpunit --colors BowlingGameTest PHPUnit 3.4.0 by Sebastian Bergmann. .F Time: 0 seconds There was 1 failure: 1) testScoreForAllOnesIs20(BowlingGameTest) Failed asserting that <integer:0> matches expected value <integer:20>. /home/sb/BowlingGameTest.php:25 FAILURES! Tests: 2, Assertions: 2, Failures: 1.
  40. 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()); } // ... }
  41. The Bowling Game Kata Introducing the rollMany() template 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()); } }
  42. 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()); } }
  43. 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()); } }
  44. 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()); } }
  45. 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) testScoreForAllOnesIs20(BowlingGameTest) Failed asserting that <integer:0> matches expected value <integer:20>. /home/sb/BowlingGameTest.php:67 2) testScoreForOneSpareAnd3Is16(BowlingGameTest) Failed asserting that <integer:0> matches expected value <integer:16>. /home/sb/BowlingGameTest.php:75 3) testScoreForOneStrikeAnd3And4Is24(BowlingGameTest) Failed asserting that <integer:0> matches expected value <integer:24>. /home/sb/BowlingGameTest.php:84 4) testScoreForPerfectGameIs300(BowlingGameTest) Failed asserting that <integer:0> matches expected value <integer:300>. /home/sb/BowlingGameTest.php:90 FAILURES! Tests: 5, Assertions: 5, Failures: 4.
  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. 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
  53. 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) testRoll(BowlingGameTest) This test has not been implemented yet. /home/sb/BowlingGameTest.php:46 2) testScore(BowlingGameTest) This test has not been implemented yet. /home/sb/BowlingGameTest.php:56 OK, but incomplete or skipped tests! Tests: 2, Assertions: 0, Incomplete: 2.
  54. 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.
  55. Show Me A Demo!
  56. 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”.
  57. 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
  58. Test Doubles Stubbing a method with PHPUnit <?php class StubTest extends PHPUnit_Framework_TestCase { public function testStub() { } } ?>
  59. Test Doubles Stubbing a method with PHPUnit <?php class StubTest extends PHPUnit_Framework_TestCase { public function testStub() { $stub = $this->getMock('SomeClass'); } } ?>
  60. 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')); } } ?>
  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')); // Calling $stub->doSomething() will now return // 'foo'. } } ?>
  62. 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' } }
  63. 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(); // ... }
  64. 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 } }
  65. Test Doubles Mocking a method with PHPUnit <?php class ObserverTest extends PHPUnit_Framework_TestCase { public function testUpdateIsCalledOnce() { } } ?>
  66. Test Doubles Mocking a method with PHPUnit <?php class ObserverTest extends PHPUnit_Framework_TestCase { public function testUpdateIsCalledOnce() { $observer = $this->getMock( 'Observer', array('update') ); } } ?>
  67. Test Doubles Mocking a method with PHPUnit <?php class ObserverTest extends PHPUnit_Framework_TestCase { public function testUpdateIsCalledOnce() { $observer = $this->getMock( 'Observer', array('update') ); $observer->expects($this->once()) ->method('update') ->with($this->equalTo('something')); } } ?>
  68. Test Doubles Mocking a method with PHPUnit <?php class ObserverTest extends PHPUnit_Framework_TestCase { public function testUpdateIsCalledOnce() { $observer = $this->getMock( 'Observer', array('update') ); $observer->expects($this->once()) ->method('update') ->with($this->equalTo('something')); $subject = new Subject; $subject->attach($observer); $subject->doSomething(); } } ?>
  69. 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); } } } ?>
  70. 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')); } } ?>
  71. 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) ); } }
  72. PHPUnit Data Provider sb@ubuntu ~ % phpunit DataTest PHPUnit 3.4.0 by Sebastian Bergmann. ..F. Time: 0 seconds There was 1 failure: 1) testAdd(DataTest) 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.
  73. PHPUnit Test Dependencies <?php class StackTest extends PHPUnit_Framework_TestCase { public function testEmpty() { $stack = array(); $this->assertTrue(empty($stack)); return $stack; } }
  74. 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; } }
  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; } /** * @depends testPush */ public function testPop(array $stack) { $this->assertEquals('foo', array_pop($stack)); $this->assertTrue(empty($stack)); } }
  76. PHPUnit Test Dependencies <?php class DependencyFailureTest extends PHPUnit_Framework_TestCase { public function testOne() { $this->assertTrue(FALSE); } /** * @depends testOne */ public function testTwo() { } }
  77. 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) testOne(DependencyFailureTest) Failed asserting that <boolean:false> is true. /home/sb/DependencyFailureTest.php:6 There was 1 skipped test: 1) testTwo(DependencyFailureTest) This test depends on "DependencyFailureTest::testOne" to pass. FAILURES! Tests: 2, Assertions: 1, Failures: 1, Skipped: 1.
  78. 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
  79. PHPUnit DatabaseTestCase <?php require_once 'PHPUnit/Extensions/Database/TestCase.php'; class BankAccountDBTest extends PHPUnit_Extensions_Database_TestCase { }
  80. 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); } }
  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); } protected function getConnection() { return $this->createDefaultDBConnection($this->pdo, 'mysql'); } }
  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'); } protected function getDataSet() { return $this->createFlatXMLDataSet('/path/to/seed.xml'); } }
  83. 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>
  84. PHPUnit DatabaseTestCase <?php require_once 'PHPUnit/Extensions/Database/TestCase.php'; class BankAccountDBTest extends PHPUnit_Extensions_Database_TestCase { // ... public function testNewAccount() { } }
  85. 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); } }
  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); $set = $this->createFlatXMLDataSet( '/path/to/after-new-account.xml' ); } }
  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' ); $this->assertTablesEqual( $set->getTable('account'), $this->getConnection() ->createDataSet() ->getTable('account') ); } }
  88. 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>
  89. 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
  90. PHPUnit Integration with Selenium RC <?php require_once 'PHPUnit/Extensions/SeleniumTestCase.php'; class WebTest extends PHPUnit_Extensions_SeleniumTestCase { } ?>
  91. 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); } } ?>
  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); } public function testTitle() { $this->open('http://www.example.com/'); $this->assertTitle('Example Web Page'); } } ?>
  93. Show Me A Demo!
  94. How do we measure Software Quality?
  95. Software Metrics  A software metric is a measure of some property of a piece of software or its specifications.  „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?  C0-Coverage: Statement Coverage  C1-Coverage: Branch Coverage  C-Coverage: Path Coverage  100% Code Coverage is a required, but not a sufficient criteria for test completeness
  97. 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
  98. Software Metrics Lines of Code sb@ubuntu ~ % pear install phpunit/phploc downloading phploc-1.1.1.tgz ... Starting to download phploc-1.1.1.tgz (6,616 bytes) .....done: 6,616 bytes install ok: channel://pear.phpunit.de/phploc-1.1.1 sb@ubuntu ~ % phploc /usr/local/src/ezcomponents/trunk/Workflow phploc 1.1.1 by Sebastian Bergmann. Directories: 13 Files: 102 Lines of Code (LOC): 14138 Executable Lines of Code (ELOC): 5831 Comment Lines of Code (CLOC): 5244 Non-Comment Lines of Code (NCLOC): 8894 Interfaces: 7 Classes: 90 Non-Static Methods: 536 Static Methods: 33 Functions: 0
  99. 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
  100. Software Metrics Code Duplication sb@ubuntu ~ % pear install phpunit/phpcpd downloading phpcpd-1.1.0.tgz ... Starting to download phpcpd-1.1.0.tgz (8,226 bytes) .....done: 8,226 bytes install ok: channel://pear.phpunit.de/phpcpd-1.1.0 sb@ubuntu ~ % 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.
  101. Software Metrics Cyclomatic Complexity  Measures the complexity of a code unit  Originally defined in graph theory ccn(G) = Edges - Vertices + 2  Equivalent: Counting the branching points if, for, foreach, while, case, catch, &&, ||, ternary operator (?:)  Interpretation  Higher complexity leads to more errors  Higher complexity makes testing harder
  102. Software Metrics Cyclomatic Complexity <?php 1 function isLeapYear($year) { $result = false; 2 if ($year % 4 != 0) { $result = false; } 3 if ($year % 100 != 0) { $result = true; } 4 if ($year % 400 == 0) { $result = true; } return $result; }
  103. Software Metrics PHP_Depend  Static Analysis of PHP Code  Software Metrics  Software Visualization  Helps to identify parts of an application that should be refactored sb@ubuntu ~ % pear channel-discover pear.pdepend.org Adding Channel "pear.pdepend.org" succeeded Discovery of channel "pear.pdepend.org" succeeded sb@ubuntu ~ % pear install pdepend/PHP_Depend downloading PHP_Depend-0.9.4.tgz ... Starting to download PHP_Depend-0.9.4.tgz (161,553 bytes) ..................................done: 161,553 bytes install ok: channel://pear.pdepend.org/PHP_Depend-0.9.4
  104. Software Metrics phpmd  Static Analysis of PHP Code  Based on PHP_Depend  Reports violations of rules that operate on raw software metrics data
  105. Software Analysis PHP_CodeSniffer  Static Analysis of PHP Code  Based on ext/tokenizer  ”Sniffs”  Coding Standard  Software Metrics  Bug Patterns  Performance Patterns  ...
  106. Software Analysis 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  …
  107. QA Tools for PHP  phpunit → JUnit XML and Clover XML  phpcs → Checkstyle XML  pdepend → JDepend XML  bytekit → PMD XML  phpmd → PMD XML  phpcpd → PMD-CPD XML
  108. 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
  109. 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>
  110. 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>
  111. 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
  112. 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
  113. 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
  114. Continuous Integration phpUnderControl Customization of CruiseControl that caters to the needs of PHP projects  PHPUnit  PHPDocumentor  PHP_CodeSniffer  (PHP_Depend)  (phpmd)  (phpcpd)
  115. Show Me A Demo!
  116. The End Thank you for your interest! These slides will be posted on http://slideshare.net/sebastian_bergmann
  117. Untestable Code Sebastian Bergmann http://thePHP.cc 11:35am on July 23rd 2009 Meeting Room J2
  118. 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, 4 months ago

custom

1996 views, 2 favs, 1 embeds more stats



Now that we know how to build applications with more

More info about this document

CC Attribution-ShareAlike LicenseCC Attribution-ShareAlike License

Go to text version

  • Total Views 1996
    • 1911 on SlideShare
    • 85 from embeds
  • Comments 1
  • Favorites 2
  • Downloads 88
Most viewed embeds
  • 85 views on http://en.oreilly.com

more

All embeds
  • 85 views on http://en.oreilly.com

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