Slideshare.net (beta)

 

All comments

Add a comment on Slide 1

If you have a SlideShare account, login to comment; else you can comment as a guest


Showing 1-50 of 7 (more)

Quality Assurance in PHP Projects

From sebastian_bergmann, 1 month ago

Half-Day Tutorial at OSCON 2008

3425 views  |  0 comments  |  7 favorites  |  177 downloads  |  11 embeds (Stats)
 

Tags

oscon08 oscon testing selenium phpundercontrol phpunit php qa tdd debug

more

 
 

Groups / Events

 

 
Embed
options

More Info

This slideshow is Public
Total Views: 3425
on Slideshare: 2995
from embeds: 430

Slideshow transcript

Slide 1: Quality Assurance in PHP Projects Sebastian Bergmann http://sebastian-bergmann.de/ July 21st 2008

Slide 2: Who I am ● Sebastian Bergmann ● Involved in the PHP Project since 2000 ● Developer of PHPUnit ● Author, Consultant, Coach, Trainer

Slide 3: Quality Assurance in PHP Projects Schedule ● 08:30am – 10:00am Tutorial ● 10:00am – 10:30am Coffee Break ● 10:30am – 12:00pm Tutorial

Slide 4: Quality Assurance in PHP Projects Topics ● PHPUnit – Writing and running tests – Organizing tests into suites and groups – Refactoring, Test-Driven and Behaviour-Driven Development – Code Coverage

Slide 5: Quality Assurance in PHP Projects Topics ● Selenium – Recording and running tests with Selenium IDE – Integrating PHPUnit with Selenium RC ● Continuous Integration – phpUnderControl – PHP_CodeSniffer

Slide 6: “Hot Topics” in PHP's History From a Keynote by Derick Rethans ● Make it work ● Make it fast ● Make it scale ● Make it secure Now that we know how to build applications that "just work", are fast and scalable, as well as secure, the next logical step is to implement processes and use techniques that help as assure that the software works correctly throughout the software's lifecycle.

Slide 7: Why test? ● Companies develop more and more enterprise-critical applications with PHP. ● Tests help to make sure that these applications work correctly.

Slide 8: What needs testing? ● Web Application – Backend ● Business Logic ● Reusable Components – Frontend ● Form Processing, Templates, ... ● “Rich Interfaces” with AJAX, JSON, ... ● Feeds, Web Services, ...

Slide 9: And how do we test it? ● Web Application – Backend ● Functional Testing of the business logic with Unit Tests ● Reusable Components – External components should have their own unit tests. – Frontend ● Acceptance Tests or System Tests that are run “in the browser” ● Testing of feeds, web services, etc. with unit tests ● Compatibility Tests for Operating System / Browser combinations ● Performance Tests and Security Tests

Slide 10: Testing Software ● Component Tests – Executable code fragments, so called Unit Tests, test the correctness of parts, units, of the software (system under test) ● System Tests – Conducted on a complete, integrated system to evaluate the system's compliance with its specified requirements. (Wikipedia) ● Non-Functional Tests – Performance, Stability, Security, ...

Slide 11: Testing Software ● Developer Tests – Ensure that the code works correctly ● Acceptance Tests – Ensure that the code does what the customer wants

Slide 12: Test Tools ● To make (code) testing viable, good tool support is needed. ● This is where a testing framework such as PHPUnit comes into play. ● Requirements – Reusable test environment – Strict separation of production code and test code – Automatic execution of test code – Analysis of the result – Easy to learn to use and easy to use

Slide 13: Test Tools for PHP / Web ● Unit Tests – PHPUnit – PHPT – SimpleTest ● System Tests – Selenium ● PHPUnit + Selenium RC ● Non-Functional Tests – Performance, Load, Stress, Availability, ... ● ab, httperf, JMeter, Grinder, OpenSTA, ... – Security ● Chorizo

Slide 14: PHPUnit: Website http://www.phpunit.de/

Slide 15: PHPUnit: Installation sb@vmware ~ % pear channel-discover pear.phpunit.de Adding Channel "pear.phpunit.de" succeeded Discovery of channel "pear.phpunit.de" succeeded sb@vmware ~ % pear install phpunit/PHPUnit downloading PHPUnit-3.2.11.tgz ... Starting to download PHPUnit-3.2.11.tgz (201,578 bytes) ..........................................done: 201,578 bytes install ok: channel://pear.phpunit.de/PHPUnit-3.2.11

Slide 16: Outline ● PHPUnit – Writing tests – Running tests ● Selenium – Recording tests with the Selenium IDE – Running tests with PHPUnit and Selenium RC ● phpUnderControl – Continous Integration – Software Metrics

Slide 17: The Bowling Game Kata Introduction: Scoring Bowling ● The game consists of 10 frames as shown below. – In each frame the player has two opportunities to knock down 10 pins. – The score for the frame is the total number of pins knocked down, plus bonuses for strikes and spares.

Slide 18: The Bowling Game Kata Introduction: Scoring Bowling ● A spare is when the player knocks down all 10 pins in two tries. – The bonus for that frame is the number of pins knocked down by the next roll. – So in frame 3 below, the score is 10 (the total number knocked down) plus a bonus of 5 (the number of pins knocked down on the next roll).

Slide 19: The Bowling Game Kata Introduction: Scoring Bowling ● A strike is when the player knocks down all 10 pins on his first try. – The bonus for that frame is the value of the next two balls rolled.

Slide 20: The Bowling Game Kata Introduction: Scoring Bowling ● In the tenth frame a player who rolls a spare or strike is allowed to roll the extra balls to complete the frame. ● However no more than three balls can be rolled in tenth frame.

Slide 21: The Bowling Game Kata Introduction: Requirements ● Write a class named BowlingGame that has two methods – roll($pins) is called each time the player rolls a ball. ● The argument is the number of pins knocked down. – score() is called only at the very end of the game and returns the total score for that game.

Slide 22: The Bowling Game Kata Introduction: Design ● A game has 10 frames. – A frame has 1 or 2 rolls. – The 10th frame has 2 or 3 rolls and is different from all the other frames. ● The score() method must iterate over all the frames and calculate all their scores. – The score for a spare or a strike depends on the frame's successor.

Slide 23: The Bowling Game Kata The first test <?php require_once 'BowlingGame.php'; class BowlingGameTest extends PHPUnit_Framework_TestCase { }

Slide 24: The Bowling Game Kata The first test <?php require_once 'BowlingGame.php'; class BowlingGameTest extends PHPUnit_Framework_TestCase { public function testGutterGame() { } }

Slide 25: The Bowling Game Kata The first test <?php require_once 'BowlingGame.php'; class BowlingGameTest extends PHPUnit_Framework_TestCase { public function testGutterGame() { $game = new BowlingGame; for ($i = 0; $i < 20; $i++) { $game->roll(0); } $this->assertEquals(0, $game->score()); } }

Slide 26: The Bowling Game Kata The first test <?php class BowlingGame { public function roll($pins) { } public function score() { return 0; } }

Slide 27: The Bowling Game Kata The first test sb@vmware ~ % phpunit BowlingGameTest PHPUnit 3.3.0 by Sebastian Bergmann. . Time: 0 seconds OK (1 test, 1 assertions)

Slide 28: The Bowling Game Kata The first test sb@vmware ~ % phpunit BowlingGameTest --ansi BowlingGameTest PHPUnit 3.3.0 by Sebastian Bergmann. . Time: 0 seconds 1 OK (1 test, 0 assertions)

Slide 29: Interlude Test-Driven Development ● Method of designing software, not just a method of testing software – Test ● What do we want X to do? ● How do we want to tell X to do it? ● How will we know when X has done it? – Code ● How does X do it? ● Tests drive the development – Tests written before code – No code without tests This slide contains material by Brady Kelly.

Slide 30: The Bowling Game Kata The first test sb@vmware ~ % phpunit --skeleton-class BowlingGameTest PHPUnit 3.3.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.'); } } ?>

Slide 31: The Bowling Game Kata The second test <?php require_once 'BowlingGame.php'; class BowlingGameTest extends PHPUnit_Framework_TestCase { // ... public function testAllOnes() { $game = new BowlingGame; for ($i = 0; $i < 20; $i++) { $game->roll(1); } $this->assertEquals(20, $game->score()); } }

Slide 32: The Bowling Game Kata The second test sb@vmware ~ % phpunit BowlingGameTest PHPUnit 3.3.0 by Sebastian Bergmann. .F Time: 0 seconds There was 1 failure: 1) testAllOnes(BowlingGameTest) Failed asserting that <integer:0> matches expected value <integer:20>. /home/sb/BowlingGameTest.php:25 FAILURES! Tests: 2, Assertions: 2, Failures: 1.

Slide 33: The Bowling Game Kata The second test sb@vmware ~ % phpunit BowlingGameTest --ansi BowlingGameTest PHPUnit 3.3.0 by Sebastian Bergmann. .F F Time: 0 seconds There was 1 failure: 1) testGutterGame(BowlingGameTest) testAllOnes(BowlingGameTest) Failed asserting that <null> matches expected valuevalue <integer:20>. <integer:0> matches expected <integer:0>. /home/sb/BowlingGameTest.php:25 /home/sb/BowlingGameTest.php:14 FAILURES! Tests: 1, Assertions: 1, Failures: 1. 2, Failures: 1.2,

Slide 34: The Bowling Game Kata The second test <?php class BowlingGame { protected $score = 0; public function roll($pins) { $this->score += $pins; } public function score() { return $this->score; } }

Slide 35: The Bowling Game Kata The second test sb@vmware ~ % phpunit BowlingGameTest PHPUnit 3.3.0 by Sebastian Bergmann. .. Time: 0 seconds OK (2 tests, 2 assertions)

Slide 36: The Bowling Game Kata The second test <?php require_once 'BowlingGame.php'; class BowlingGameTest extends PHPUnit_Framework_TestCase { // ... public function testAllOnes() { $game = new BowlingGame; for ($i = 0; $i < 20; $i++) { $game->roll(1); } $this->assertEquals(20, $game->score()); } }

Slide 37: The Bowling Game Kata The second test <?php require_once 'BowlingGame.php'; class BowlingGameTest extends PHPUnit_Framework_TestCase { // ... public function testAllOnes() { $game = new BowlingGame; for ($i = 0; $i < 20; $i++) { $game->roll(1); } $this->assertEquals(20, $game->score()); } }

Slide 38: Interlude Test Fixture ● One of the most time-consuming parts of writing tests is writing the code to set the world up in a known state and then return it to its original state when the test is complete. ● This known state is called the fixture of the test.

Slide 39: Interlude Test Fixture ● In our BowlingGameTest example the test fixture was a single object. ● Most of the time, though, the fixture will be more complex than a simple object, and the amount of code needed to set it up will grow accordingly. ● The actual content of the test gets lost in the noise of setting up the fixture. – This problem gets even worse when you write several tests with similar fixtures. ● PHPUnit supports sharing the setup code through the setUp() and tearDown() template methods.

Slide 40: The Bowling Game Kata The second test <?php require_once 'BowlingGame.php'; class BowlingGameTest extends PHPUnit_Framework_TestCase { protected $game; protected function setUp() { $this->game = new BowlingGame; } // ... public function testAllOnes() { for ($i = 0; $i < 20; $i++) { $this->game->roll(1); } $this->assertEquals(20, $this->game->score()); } // ... }

Slide 41: The Bowling Game Kata The second test <?php require_once 'BowlingGame.php'; class BowlingGameTest extends PHPUnit_Framework_TestCase { protected $game; protected function setUp() { $this->game = new BowlingGame; } // ... public function testAllOnes() { for ($i = 0; $i < 20; $i++) { $this->game->roll(1); } $this->assertEquals(20, $this->game->score()); } // ... }

Slide 42: The Bowling Game Kata The second test <?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 testGutterGame() { $this->rollMany(20, 0); $this->assertEquals(0, $this->game->score()); } public function testAllOnes() { $this->rollMany(20, 1); $this->assertEquals(20, $this->game->score()); } }

Slide 43: The Bowling Game Kata The second test <?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 testGutterGame() { $this->rollMany(20, 0); $this->assertEquals(0, $this->game->score()); } public function testAllOnes() { $this->rollMany(20, 1); $this->assertEquals(20, $this->game->score()); } }

Slide 44: Interlude Refactoring ● A code refactoring is any change to a computer program's code which improves its readability or simplifies its structure without changing its results. (Wikipedia) 1.All unit tests run correctly. 2.The code communicates its design principles. 3.The code contains no redundancies. 4.The code contains the minimal number of classes and methods.

Slide 45: The Bowling Game Kata The third test <?php require_once 'BowlingGame.php'; class BowlingGameTest extends PHPUnit_Framework_TestCase { // ... public function testOneSpare() { $this->game->roll(5); $this->game->roll(5); // Spare $this->game->roll(3); $this->rollMany(17, 0); $this->assertEquals(16, $this->game->score()); } }

Slide 46: The Bowling Game Kata The third test sb@vmware ~ % phpunit BowlingGameTest PHPUnit 3.3.0 by Sebastian Bergmann. ..F Time: 0 seconds There was 1 failure: 1) testOneSpare(BowlingGameTest) Failed asserting that <integer:13> matches expected value <integer:16>. /home/sb/BowlingGameTest.php:38 FAILURES! Tests: 3, Assertions: 3, Failures: 1.

Slide 47: The Bowling Game Kata The third test <?php class BowlingGame { protected $score = 0; public function roll($pins) { $this->score += $pins; } public function score() { return $this->score; } }

Slide 48: The Bowling Game Kata The third test <?php class BowlingGame { protected $score = 0; protected $rolls = array(); public function roll($pins) { $this->score += $pins; $this->rolls[] = $pins; } public function score() { return $this->score; } }

Slide 49: The Bowling Game Kata The third test <?php class BowlingGame { protected $rolls = array(); public function roll($pins) { $this->rolls[] = $pins; } public function score() { $score = 0; foreach ($this->rolls as $roll) { $score += $roll; } return $score; } }

Slide 50: The Bowling Game Kata The third test sb@vmware ~ % phpunit BowlingGameTest PHPUnit 3.3.0 by Sebastian Bergmann. ..F Time: 0 seconds There was 1 failure: 1) testOneSpare(BowlingGameTest) Failed asserting that <integer:13> matches expected value <integer:16>. /home/sb/BowlingGameTest.php:38 FAILURES! Tests: 3, Assertions: 3, Failures: 1.

Slide 51: The Bowling Game Kata The third test <?php class BowlingGame { // ... public function score() { $score = 0; for ($i = 0; $i < count($this->rolls); $i++) { if ($this->rolls[$i] + $this->rolls[$i + 1] == 10) { // ... } $score += $this->rolls[$i]; } return $score; } }

Slide 52: The Bowling Game Kata The third test <?php class BowlingGame { // ... public function score() { $score = 0; $i = 0; for ($frame = 0; $frame < 10; $frame++) { $score += $this->rolls[$i] + $this->rolls[$i + 1]; $i += 2; } return $score; } }

Slide 53: The Bowling Game Kata The third test sb@vmware ~ % phpunit BowlingGameTest PHPUnit 3.3.0 by Sebastian Bergmann. ..F Time: 0 seconds There was 1 failure: 1) testOneSpare(BowlingGameTest) Failed asserting that <integer:13> matches expected value <integer:16>. /home/sb/BowlingGameTest.php:38 FAILURES! Tests: 3, Assertions: 3, Failures: 1.

Slide 54: The Bowling Game Kata The third test <?php class BowlingGame { // ... public function score() { $score = 0; $i = 0; for ($frame = 0; $frame < 10; $frame++) { // Spare if ($this->rolls[$i] + $this->rolls[$i + 1] == 10) { $score += 10 + $this->rolls[$i + 2]; } else { $score += $this->rolls[$i] + $this->rolls[$i + 1]; } $i += 2; } return $score; } }

Slide 55: The Bowling Game Kata The third test sb@vmware ~ % phpunit BowlingGameTest PHPUnit 3.3.0 by Sebastian Bergmann. ... Time: 0 seconds OK (3 tests, 3 assertions)

Slide 56: The Bowling Game Kata The third test <?php class BowlingGame { // ... public function score() { $score = 0; $i = 0; for ($frame = 0; $frame < 10; $frame++) { // Spare if ($this->rolls[$i] + $this->rolls[$i + 1] == 10) { $score += 10 + $this->rolls[$i + 2]; } else { $score += $this->rolls[$i] + $this->rolls[$i + 1]; } $i += 2; } return $score; } }

Slide 57: The Bowling Game Kata The third test <?php class BowlingGame { // ... public function score() { $score = 0; $i = 0; for ($frame = 0; $frame < 10; $frame++) { // Spare if ($this->rolls[$i] + $this->rolls[$i + 1] == 10) { $score += 10 + $this->rolls[$i + 2]; } else { $score += $this->rolls[$i] + $this->rolls[$i + 1]; } $i += 2; } return $score; } }

Slide 58: The Bowling Game Kata The third test <?php class BowlingGame { // ... public function score() { $score = 0; $frameIndex = 0; for ($frame = 0; $frame < 10; $frame++) { // Spare if ($this->rolls[$frameIndex] + $this->rolls[$frameIndex + 1] == 10) { $score += 10 + $this->rolls[$frameIndex + 2]; } else { $score += $this->rolls[$frameIndex] + $this->rolls[$frameIndex + 1]; } $frameIndex += 2; } return $score; } }

Slide 59: The Bowling Game Kata The third test <?php class BowlingGame { // ... public function score() { $score = 0; $frameIndex = 0; for ($frame = 0; $frame < 10; $frame++) { if ($this->isSpare($frameIndex)) { $score += 10 + $this->rolls[$frameIndex + 2]; } else { $score += $this->rolls[$frameIndex] + $this->rolls[$frameIndex + 1]; } $frameIndex += 2; } return $score; } protected function isSpare($frameIndex) { return $this->rolls[$frameIndex] + $this->rolls[$frameIndex + 1] == 10; } }

Slide 60: The Bowling Game Kata The third test <?php require_once 'BowlingGame.php'; class BowlingGameTest extends PHPUnit_Framework_TestCase { // ... public function testOneSpare() { $this->game->roll(5); $this->game->roll(5); // Spare $this->game->roll(3); $this->rollMany(17, 0); $this->assertEquals(16, $this->game->score()); } }

Slide 61: The Bowling Game Kata The third test <?php require_once 'BowlingGame.php'; class BowlingGameTest extends PHPUnit_Framework_TestCase { // ... public function testOneSpare() { $this->game->roll(5); $this->game->roll(5); // Spare $this->game->roll(3); $this->rollMany(17, 0); $this->assertEquals(16, $this->game->score()); } }

Slide 62: The Bowling Game Kata The third test <?php require_once 'BowlingGame.php'; class BowlingGameTest extends PHPUnit_Framework_TestCase { // ... protected function rollSpare() { $this->game->roll(5); $this->game->roll(5); } // ... public function testOneSpare() { $this->rollSpare(); $this->game->roll(3); $this->rollMany(17, 0); $this->assertEquals(16, $this->game->score()); } }

Slide 63: The Bowling Game Kata The fourth test <?php require_once 'BowlingGame.php'; class BowlingGameTest extends PHPUnit_Framework_TestCase { // ... public function testOneStrike() { $this->game->roll(10); // Strike $this->game->roll(3); $this->game->roll(4); $this->rollMany(17, 0); $this->assertEquals(24, $this->game->score()); } }

Slide 64: The Bowling Game Kata The fourth test sb@vmware ~ % phpunit BowlingGameTest PHPUnit 3.3.0 by Sebastian Bergmann. ...F Time: 0 seconds There was 1 failure: 1) testOneStrike(BowlingGameTest) Failed asserting that <integer:17> matches expected value <integer:24>. /home/sb/BowlingGameTest.php:52 FAILURES! Tests: 4, Assertions: 4, Failures: 1.

Slide 65: The Bowling Game Kata The fourth test <?php class BowlingGame { // ... public function score() { $score = 0; $frameIndex = 0; for ($frame = 0; $frame < 10; $frame++) { if ($this->isSpare($frameIndex)) { $score += 10 + $this->rolls[$frameIndex + 2]; } else { $score += $this->rolls[$frameIndex] + $this->rolls[$frameIndex + 1]; } $frameIndex += 2; } return $score; } }

Slide 66: The Bowling Game Kata The fourth test <?php class BowlingGame { // ... public function score() { $score = 0; $frameIndex = 0; for ($frame = 0; $frame < 10; $frame++) { // Strike if ($this->rolls[$frameIndex] == 10) { $score += 10 + $this->rolls[$frameIndex + 1] + $this->rolls[$frameIndex + 2]; $frameIndex++; } // ... } return $score; } }

Slide 67: The Bowling Game Kata The fourth test sb@vmware ~ % phpunit BowlingGameTest PHPUnit 3.3.0 by Sebastian Bergmann. .... Time: 0 seconds OK (4 tests, 4 assertions)

Slide 68: The Bowling Game Kata The fourth test <?php class BowlingGame { // ... public function score() { $score = 0; $frameIndex = 0; for ($frame = 0; $frame < 10; $frame++) { // Strike if ($this->rolls[$frameIndex] == 10) { $score += 10 + $this->rolls[$frameIndex + 1] + $this->rolls[$frameIndex + 2]; $frameIndex++; } // ... } return $score; } }

Slide 69: The Bowling Game Kata The fourth test <?php class BowlingGame { // ... public function score() { $score = 0; $frameIndex = 0; for ($frame = 0; $frame < 10; $frame++) { if ($this->isStrike($frameIndex)) { $score += 10 + $this->rolls[$frameIndex + 1] + $this->rolls[$frameIndex + 2]; $frameIndex++; } // ... } return $score; } protected function isStrike($frameIndex) { return $this->rolls[$frameIndex] == 10; } }

Slide 70: The Bowling Game Kata The fourth test <?php require_once 'BowlingGame.php'; class BowlingGameTest extends PHPUnit_Framework_TestCase { // ... public function testOneStrike() { $this->game->roll(10); // Strike $this->game->roll(3); $this->game->roll(4); $this->rollMany(17, 0); $this->assertEquals(24, $this->game->score()); } }

Slide 71: The Bowling Game Kata The fourth test <?php require_once 'BowlingGame.php'; class BowlingGameTest extends PHPUnit_Framework_TestCase { // ... protected function rollStrike() { $this->game->roll(10); } // ... public function testOneStrike() { $this->rollStrike(); $this->game->roll(3); $this->game->roll(4); $this->rollMany(17, 0); $this->assertEquals(24, $this->game->score()); } }

Slide 72: The Bowling Game Kata The fourth test sb@vmware ~ % phpunit BowlingGameTest PHPUnit 3.3.0 by Sebastian Bergmann. .... Time: 0 seconds OK (4 tests, 4 assertions)

Slide 73: The Bowling Game Kata The fifth test <?php require_once 'BowlingGame.php'; class BowlingGameTest extends PHPUnit_Framework_TestCase { // ... public function testPerfectGame() { $this->rollMany(12, 10); $this->assertEquals(300, $this->game->score()); } }

Slide 74: The Bowling Game Kata The fifth test sb@vmware ~ % phpunit BowlingGameTest PHPUnit 3.3.0 by Sebastian Bergmann. ..... Time: 0 seconds OK (5 tests, 5 assertions)

Slide 75: The Bowling Game Kata Final version of the BowlingGame class <?php class BowlingGame { protected $rolls = array(); public function roll($pins) { $this->rolls[] = $pins; } // ... }

Slide 76: The Bowling Game Kata Final version of 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]; } }

Slide 77: The Bowling Game Kata Final version of 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]; } }

Slide 78: The Bowling Game Kata Final version of 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; } }

Slide 79: The Bowling Game Kata The fifth test sb@vmware ~ % phpunit BowlingGameTest PHPUnit 3.3.0 by Sebastian Bergmann. ..... Time: 0 seconds OK (5 tests, 5 assertions)

Slide 80: PHPUnit Agile Documentation <?php require_once 'BowlingGame.php'; class BowlingGameTest extends PHPUnit_Framework_TestCase { // ... public function testGutterGame() { // ... } public function testAllOnes() { // ... } public function testOneSpare() { // ... } public function testOneStrike() { // ... } public function testPerfectGame() { // ... } }

Slide 81: PHPUnit Agile Documentation <?php require_once 'BowlingGame.php'; class BowlingGameTest extends PHPUnit_Framework_TestCase { // ... public function testScoreForGutterGameIs0() { // ... } public function testScoreForAllOnesIs20() { // ... } public function testScoreForOneSpareAnd3Is16() { // ... } public function testScoreForOneStrikeAnd3And4Is24() { // ... } public function testScoreForPerfectGameIs300() { // ... } }

Slide 82: PHPUnit Agile Documentation sb@vmware ~ % phpunit --testdox BowlingGameTest PHPUnit 3.3.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

Slide 83: Behaviour-Driven Development ● Extreme Programming originally had the rule to test everything that could possibly break ● Now, however, the practice of testing in Extreme Programming has evolved into Test-Driven Development ● But the tools still force developers to think in terms of tests and assertions instead of specifications A New Look At Test-Driven Development, Dave Astels. http://blog.daveastels.com/files/BDD_Intro.pdf

Slide 84: Behaviour-Driven Development <?php require_once 'PHPUnit/Extensions/Story/TestCase.php'; require_once 'BowlingGame.php'; class BowlingGameSpec extends PHPUnit_Extensions_Story_TestCase { }

Slide 85: Behaviour-Driven Development <?php require_once 'PHPUnit/Extensions/Story/TestCase.php'; require_once 'BowlingGame.php'; class BowlingGameSpec extends PHPUnit_Extensions_Story_TestCase { /** * @scenario */ public function scoreForOneSpareAnd3Is16() { } // ... }

Slide 86: Behaviour-Driven Development <?php require_once 'PHPUnit/Extensions/Story/TestCase.php'; require_once 'BowlingGame.php'; class BowlingGameSpec extends PHPUnit_Extensions_Story_TestCase { /** * @scenario */ public function scoreForOneSpareAnd3Is16() { $this->given('New game') } // ... }

Slide 87: Behaviour-Driven Development <?php require_once 'PHPUnit/Extensions/Story/TestCase.php'; require_once 'BowlingGame.php'; class BowlingGameSpec extends PHPUnit_Extensions_Story_TestCase { /** * @scenario */ public function scoreForOneSpareAnd3Is16() { $this->given('New game') ->when('Player rolls', 5) } // ... }

Slide 88: Behaviour-Driven Development <?php require_once 'PHPUnit/Extensions/Story/TestCase.php'; require_once 'BowlingGame.php'; class BowlingGameSpec extends PHPUnit_Extensions_Story_TestCase { /** * @scenario */ public function scoreForOneSpareAnd3Is16() { $this->given('New game') ->when('Player rolls', 5) ->and('Player rolls', 5) } // ... }

Slide 89: Behaviour-Driven Development <?php require_once 'PHPUnit/Extensions/Story/TestCase.php'; require_once 'BowlingGame.php'; class BowlingGameSpec extends PHPUnit_Extensions_Story_TestCase { /** * @scenario */ public function scoreForOneSpareAnd3Is16() { $this->given('New game') ->when('Player rolls', 5) ->and('Player rolls', 5) ->and('Player rolls', 3) } // ... }

Slide 90: Behaviour-Driven Development <?php require_once 'PHPUnit/Extensions/Story/TestCase.php'; require_once 'BowlingGame.php'; class BowlingGameSpec extends PHPUnit_Extensions_Story_TestCase { /** * @scenario */ public function scoreForOneSpareAnd3Is16() { $this->given('New game') ->when('Player rolls', 5) ->and('Player rolls', 5) ->and('Player rolls', 3) ->then('Score should be', 16); } // ... }

Slide 91: Behaviour-Driven Development <?php require_once 'PHPUnit/Extensions/Story/TestCase.php'; require_once 'BowlingGame.php'; class BowlingGameSpec extends PHPUnit_Extensions_Story_TestCase { // ... public function runGiven(&$world, $action, $arguments) { switch($action) { case 'New game': { $world['game'] = new BowlingGame; $world['rolls'] = 0; } break; default: { return $this->notImplemented($action); } } } }

Slide 92: Behaviour-Driven Development <?php require_once 'PHPUnit/Extensions/Story/TestCase.php'; require_once 'BowlingGame.php'; class BowlingGameSpec extends PHPUnit_Extensions_Story_TestCase { // ... public function runWhen(&$world, $action, $arguments) { switch($action) { case 'Player rolls': { $world['game']->roll($arguments[0]); $world['rolls']++; } break; default: { return $this->notImplemented($action); } } } }

Slide 93: Behaviour-Driven Development <?php require_once 'PHPUnit/Extensions/Story/TestCase.php'; require_once 'BowlingGame.php'; class BowlingGameSpec extends PHPUnit_Extensions_Story_TestCase { // ... public function runThen(&$world, $action, $arguments) { switch($action) { case 'Score should be': { for ($i = $world['rolls']; $i < 20; $i++) { $world['game']->roll(0); } $this->assertEquals($arguments[0], $world['game']->score()); } break; default: { return $this->notImplemented($action); } } } }

Slide 94: Behaviour-Driven Development sb@vmware ~ % phpunit --story BowlingGameSpec PHPUnit 3.3.0 by Sebastian Bergmann. BowlingGameSpec - Score for one spare and 3 is 16 [successful] Given New game When Player rolls 5 and Player rolls 5 and Player rolls 3 Then Score should be 16 Scenarios: 1, Failed: 0, Skipped: 0, Incomplete: 0. sb@vmware ~ % phpunit --testdox BowlingGameSpec PHPUnit 3.3.0 by Sebastian Bergmann. BowlingGameSpec [x] Score for one spare and 3 is 16

Slide 95: PHPUnit Code Coverage sb@vmware ~ % phpunit --coverage-html report BowlingGameTest PHPUnit 3.3.0 by Sebastian Bergmann. ..... Time: 0 seconds OK (5 tests) Generating report, this may take a moment.

Slide 96: Organizing Test Suites Application/ – Package/ ● Application_Package_Class Application/Package/Class.php ● ... – ... – Tests/ ● AllTests.php ● Package/ – AllTests.php – Application_Package_ClassTest Application/Tests/Package/ClassTest.php

Slide 97: Organizing Test Suites Application/Tests/AllTests.php <?php require_once 'PHPUnit/Framework.php'; require_once 'Application/Tests/Package/AllTests.php'; class AllTests { public static function suite() { $suite = new PHPUnit_Framework_TestSuite('Project'); $suite->addTest(Package_AllTests::suite()); return $suite; } } ?>

Slide 98: Organizing Test Suites Application/Tests/Package/AllTests.php <?php require_once 'PHPUnit/Framework.php'; require_once 'Application/Tests/Package/ClassTest.php'; class Package_AllTests { public static function suite() { $suite = new PHPUnit_Framework_TestSuite('Package'); $suite->addTestSuite('Package_ClassTest'); return $suite; } } ?>

Slide 99: Organizing Test Suites Application/Tests/Package/ClassTest.php <?php require_once 'PHPUnit/Framework.php'; require_once 'Application/Package/ClassTest.php'; class Package_ClassTest extends PHPUnit_Framework_TestCase { public function testSomething() { // ... } } ?>

Slide 100: Organizing Test Suites Running the tests ● Executing phpunit AllTests in the Tests directory will run all tests. ● Executing phpunit AllTests in the Tests/Package directory will run only the tests for the Application_Package_* classes. ● Executing phpunit ClassTest in the Tests/Framework directory will run only the tests for the Application_Package_Class class (which are declared in the Package_ClassTest class). ● Executing phpunit --filter testSomething ClassTest in the Tests/Package directory will run only the test named testSomething from the Package_ClassTest class.

Slide 101: Organizing Test Suites The @group annotation <?php class SomeTest extends PHPUnit_Framework_TestCase { /** * @group specification */ public function testSomething() { } /** * @group regresssion * @group bug2204 */ public function testSomethingElse() { } }

Slide 102: Organizing Test Suites The @group annotation sb@vmware ~ % phpunit SomeTest PHPUnit 3.3.0 by Sebastian Bergmann. .. Time: 0 seconds OK (2 tests, 2 assertions) sb@vmware ~ % phpunit --group bug2204 SomeTest PHPUnit 3.3.0 by Sebastian Bergmann. . Time: 0 seconds OK (1 test, 1 assertion)

Slide 103: Annotations @dataProvider <?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) ); } }

Slide 104: Annotations @dataProvider sb@vmware ~ % phpunit DataTest PHPUnit 3.3.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.

Slide 105: Annotations @expectedException <?php class ExceptionTest extends PHPUnit_Framework_TestCase { /** * @expectedException InvalidArgumentException */ public function testException() { } }

Slide 106: Annotations @expectedException sb@vmware ~ % phpunit ExceptionTest PHPUnit 3.3.0 by Sebastian Bergmann. F Time: 0 seconds There was 1 failure: 1) testException(ExceptionTest) Expected exception InvalidArgumentException FAILURES! Tests: 1, Assertions: 1, Failures: 1.

Slide 107: Annotations @test <?php class Specification extends PHPUnit_Framework_TestCase { /** * @test */ public function shouldDoSomething() { } /** * @test */ public function shouldDoSomethingElse() { } }

Slide 108: Annotations @test sb@vmware ~ % phpunit --testdox Specification PHPUnit 3.3.0 by Sebastian Bergmann. Specification [x] Should do something [x] Should do something else

Slide 109: Annotations @assert <?php class Calculator { /** * @assert (1, 2) == 3 */ public function add($a, $b) { return $a + $b; } /** * @assert (2, 1) == 1 */ public function sub($a, $b) { return $a - $b; } }

Slide 110: Annotations @assert sb@vmware ~ % phpunit Calculator PHPUnit 3.3.0 by Sebastian Bergmann. .. Time: 0 seconds OK (2 tests, 2 assertions)

Slide 111: 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”.

Slide 112: 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

Slide 113: Test Doubles Stubs <?php require_once 'PHPUnit/Framework.php'; class StubTest extends PHPUnit_Framework_TestCase { public function testStub() { } } ?>

Slide 114: Test Doubles Stubs <?php require_once 'PHPUnit/Framework.php'; class StubTest extends PHPUnit_Framework_TestCase { public function testStub() { $stub = $this->getMock('SomeClass'); } } ?>

Slide 115: Test Doubles Stubs: returnValue() <?php require_once 'PHPUnit/Framework.php'; class StubTest extends PHPUnit_Framework_TestCase { public function testStub() { $stub = $this->getMock('SomeClass'); $stub->expects($this->any()) ->method('doSomething') ->will($this->returnValue('foo')); } } ?>

Slide 116: Test Doubles Stubs: returnValue() <?php require_once 'PHPUnit/Framework.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'. } } ?>

Slide 117: Test Doubles Stubs: 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' } }

Slide 118: Test Doubles Stubs: 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(); // ... }

Slide 119: Test Doubles Stubs: 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 } }

Slide 120: Test Doubles Mock Objects <?php require_once 'PHPUnit/Framework.php'; class ObserverTest extends PHPUnit_Framework_TestCase { public function testUpdateIsCalledOnce() { } } ?>

Slide 121: Test Doubles Mock Objects <?php require_once 'PHPUnit/Framework.php'; class ObserverTest extends PHPUnit_Framework_TestCase { public function testUpdateIsCalledOnce() { $observer = $this->getMock( 'Observer', array('update') ); } } ?>

Slide 122: Test Doubles Mock Objects <?php require_once 'PHPUnit/Framework.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')); } } ?>

Slide 123: Test Doubles Mock Objects <?php require_once 'PHPUnit/Framework.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(); } } ?>

Slide 124: DbUnit ● Michael Lively Jr. has ported the DbUnit extension for JUnit to PHPUnit – PHPUnit_Extensions_Database_TestCase ● is used to test database-driven projects and ● puts your database into a known state between test runs – This avoids problems with one test corrupting the database for other tests ● has the ability to export and import your database data to and from XML datasets

Slide 125: DbUnit ● DbUnit uses PDO to connect to the database-under-test ● The tested application does not have to use PDO itself for this to work – You can therefore use ext/mysqli in your application and ext/pdo_mysql in your tests, for instance

Slide 126: DbUnit BankAccountDBTest.php <?php require_once 'PHPUnit/Extensions/Database/TestCase.php'; class BankAccountDBTest extends PHPUnit_Extensions_Database_TestCase { }

Slide 127: DbUnit BankAccountDBTest.php <?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); } }

Slide 128: DbUnit BankAccountDBTest.php <?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'); } }

Slide 129: DbUnit BankAccountDBTest.php <?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'); } }

Slide 130: DbUnit seed.xml <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>

Slide 131: DbUnit BankAccountDBTest.php <?php require_once 'PHPUnit/Extensions/Database/TestCase.php'; class BankAccountDBTest extends PHPUnit_Extensions_Database_TestCase { // ... public function testNewAccount() { } }

Slide 132: DbUnit BankAccountDBTest.php <?php require_once 'PHPUnit/Extensions/Database/TestCase.php'; class BankAccountDBTest extends PHPUnit_Extensions_Database_TestCase { // ... public function testNewAccount() { $ba = new BankAccountDB('12345678912345678', $this->pdo); } }

Slide 133: DbUnit BankAccountDBTest.php <?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' ); } }

Slide 134: DbUnit BankAccountDBTest.php <?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') ); } }

Slide 135: DbUnit after-new-account.xml <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>

Slide 136: Test against SQLite if you can ● 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

Slide 137: Selenium ● Selenium – Test web applications in a web browser ● Browser Compatibility Testing ● System Functional Testing – Runs in the browser ● Selenium IDE – IDE for Selenium tests ● Extension for Firefox ● Record, execute, edit, debug tests in the browser

Slide 138: Selenium Selenium RC ● Selenium RC – Automated execution of Selenium tests – Tests can be specified in any language ● PHP Bindings: PEAR Testing_Selenium ● PHPUnit natively speaks the Selenium RC protocol