By Sebastian Bergmann
Book Price: $9.95 USD
£6.95 GBP
PDF Price: $7.99
Cover | Table of Contents
http://www.phpunit.de/pocket_guide/. You may distribute and make changes to this book however you wish. Of course, rather than distribute your own private version of the book, I would prefer you send feedback and patches to sb@sebastian-bergmann.de.Constant width
http://www.phpunit.de/pocket_guide/. You may distribute and make changes to this book however you wish. Of course, rather than distribute your own private version of the book, I would prefer you send feedback and patches to sb@sebastian-bergmann.de.Constant width
Constant width bold
Constant width italic
http://www.oreilly.com/catalog/pupg
http://www.oreilly.com
print-based testing code to a fully automated test. Imagine that we have been asked to test PHP's built-in Array. One bit of functionality to test is the function sizeof( ). For a newly created array, we expect the sizeof( ) function to return 0. After we add an element, sizeof( ) should return 1. Example 1 shows what we want to test.<?php $fixture = Array( ); // $fixture is expected to be empty. $fixture[] = "element"; // $fixture is expected to contain one element. ?>
sizeof( ) before and after adding the element (see Example 2). If we get 0 and then 1, Array and sizeof( ) are behaving as expected.<?php $fixture = Array( ); print sizeof($fixture) . "\n"; $fixture[] = "element"; print sizeof($fixture) . "\n"; ?> 0 1
Array built-in and the sizeof( ) function. When we start to test the numerous array_*( ) functions PHP offers, we will need to write a test for each of them. We could write all these tests from scratch. However, it is much better to write a testing infrastructure once and then write only the unique parts of each test. PHPUnit is such an infrastructure.
<?php
require_once 'PHPUnit2/Framework/TestCase.php';
class ArrayTest extends PHPUnit2_Framework_TestCase {
public function testNewArrayIsEmpty( ) {
// Create the Array fixture.
$fixture = Array( );
// Assert that the size of the Array fixture is 0.
$this->assertEquals(0, sizeof($fixture));
}
public function testArrayContainsAnElement( ) {
// Create the Array fixture.
$fixture = Array( );
// Add an element to the Array fixture.
$fixture[] = 'Element';
// Assert that the size of the Array fixture is 1.
$this->assertEquals(1, sizeof($fixture));
}
}
?>
Class go into a class ClassTest.ClassTest inherits (most of the time) from PHPUnit2_ Framework_TestCase.test*.assertEquals( ) (see Table 6) are used to assert that an actual value matches an expected value.
$ pear install PHPUnit2
http://pear.php.net/package/PHPUnit2/download and extract it to a directory that is listed in the include_path of your php.ini configuration file.@php_bin@ string in it with the path to your PHP command-line interpreter (usually /usr/bin/ php).chmod +x phpunit).@package_version@ string in the PHPUnit2/ Runner/Version.php script with the version number of the PHPUnit release you are installing (2.3.0, for instance).
phpunit ArrayTest
PHPUnit 2.3.0 by Sebastian Bergmann.
..
Time: 0.067288
OK (2 tests)
F Printed when an assertion fails while running the test method.E Printed when an error occurs while running the test method.I Printed when the test is marked as being incomplete or not yet implemented (see "Incomplete Tests," later in this book).
phpunit --help
PHPUnit 2.3.0 by Sebastian Bergmann.
Usage: phpunit [switches] UnitTest [UnitTest.php]
--coverage-data <file> Write code-coverage data in raw
format to file.
--coverage-html <file> Write code-coverage data in HTML
format to file.
--coverage-text <file> Write code-coverage data in text
format to file.
--testdox-html <file> Write agile documentation in HTML
format to file.
--testdox-text <file> Write agile documentation in Text
format to file.
--log-xml <file> Log test progress in XML format
to file.
--loader <loader> TestSuiteLoader implementation to
use.
--skeleton Generate skeleton UnitTest class
for Unit in Unit.php.
--wait Waits for a keystroke after each
test.
--help Prints this usage information.
--version Prints the version and exits.
$fixture variable. Most of the time, though, the fixture will be more complex than a simple array, 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. Without some help from the testing framework, we would have to duplicate the code that sets up the fixture for each test we write.setUp( ) is invoked. setUp( ) is where you create the objects against which you will test. Once the test method has finished running, whether it succeeded or failed, another template method called tearDown( ) is invoked. tearDown( ) is where you clean up the objects against which you tested.setUp( ) to eliminate the code duplication that we had before. First, we declare the instance variable, $fixture, that we are going to use instead of a method-local variable. Then, we put the creation of the Array fixture into the setUp( ) method. Finally, we remove the redundant code from the test methods and use the newly introduced instance variable, $this->fixture, instead of the method-local variable $fixture with the assertEquals( ) assertion method.
<?php
require_once 'PHPUnit2/Framework/TestCase.php';
class ArrayTest extends PHPUnit2_Framework_TestCase {
protected $fixture;
protected function setUp( ) {
// Create the Array fixture.
$this->fixture = Array( );
}
public function testNewArrayIsEmpty( ) {
// Assert that the size of the Array fixture is 0.
$this->assertEquals(0, sizeof($this->fixture));
}
public function testArrayContainsAnElement( ) {
// Add an element to the Array fixture.
$this->fixture[] = 'Element';
// Assert that the size of the Array fixture is 1.
$this->assertEquals(1, sizeof($this->fixture));
}
}
?>
setUp( ) and tearDown( ) are nicely symmetrical in theory but not in practice. In practice, you only need to implement tearDown( ) if you have allocated external resources such as files or sockets in setUp( ). If your setUp( ) just creates plain PHP objects, you can generally ignore tearDown( ). However, if you create many objects in your setUp( ), you might want to unset( ) the variables pointing to those objects in your tearDown( ) so they can be garbage collected. The garbage collection of test-case objects is not predictable.setUp( ) code differs only slightly, move the code that differs from the setUp( ) code to the test method.setUp( ), you need a different test-case class. Name the class after the difference in the setup.DatabaseTests, and wrap the test suite in a TestSetup decorator object that overrides setUp( ) to open the database connection and tearDown( ) to close the connection, as shown in Example 6. You can run the tests from DatabaseTests through the DatabaseTestSetup decorator by invoking, for instance, PHPUnit's command-line test runner with phpunit DatabaseTestSetup.
<?php
require_once 'PHPUnit2/Framework/TestSuite.php';
require_once 'PHPUnit2/Extensions/TestSetup.php';
class DatabaseTestSetup extends PHPUnit2_Extensions_TestSetup
{
protected $connection = NULL;
protected function setUp( ) {
$this->connection = new PDO(
'mysql:host=wopr;dbname=test',
'root',
''
);
}
protected function tearDown( ) {
$this->connection = NULL;
}
public static function suite( ) {
return new DatabaseTestSetup(
new PHPUnit2_Framework_TestSuite('DatabaseTests')
);
}
}
?>
PHPUnit2_Framework_TestCase.
<?php
require_once 'PHPUnit2/Framework/TestCase.php';
class ExceptionTest extends PHPUnit2_Framework_TestCase {
public function testException( ) {
try {
// … Code that is expected to raise an
// Exception …
$this->fail('No Exception has been raised.');
}
catch (Exception $expected) {
}
}
}
?>
fail( ) (see Table 7, later in this book) will halt the test and signal a problem with the test. If the expected exception is raised, the catch block will be executed, and the test will continue executing.PHPUnit2_ Extensions_ExceptionTestCase to test whether an exception is thrown inside the tested code. Example 7 shows how to subclass
PHPUnit2_Extensions_ExceptionTestCase and use its setExpectedException( ) method to set the expected exception. If this expected exception is not thrown, the test will be counted as a failure.
<?php
require_once 'PHPUnit2/Extensions/ExceptionTestCase.php';
class ExceptionTest extends PHPUnit2_Extensions_
ExceptionTestCase {
public function testException( ) {
$this->setExpectedException('Exception');
}
}
?>
phpunit ExceptionTest
PHPUnit 2.3.0 by Sebastian Bergmann.
F
Time: 0.006798
There was 1 failure:
1) testException(ExceptionTest)
Expected exception Exception
FAILURES!!!
Tests run: 1, Failures: 1, Errors: 0, Incomplete Tests: 0.
PHPUnit2_Extensions_ExceptionTestCase.
<?php
require_once 'PHPUnit2/Framework/TestCase.php';
class ExceptionTest extends PHPUnit2_Framework_TestCase {
public function testException( ) {
try {
// … Code that is expected to raise an
// Exception …
$this->fail('No Exception has been raised.');
}
catch (Exception $expected) {
}
}
}
?>
fail( ) (see Table 7, later in this book) will halt the test and signal a problem with the test. If the expected exception is raised, the catch block will be executed, and the test will continue executing.PHPUnit2_ Extensions_ExceptionTestCase to test whether an exception is thrown inside the tested code. Example 7 shows how to subclass
PHPUnit2_Extensions_ExceptionTestCase and use its setExpectedException( ) method to set the expected exception. If this expected exception is not thrown, the test will be counted as a failure.
<?php
require_once 'PHPUnit2/Extensions/ExceptionTestCase.php';
class ExceptionTest extends PHPUnit2_Extensions_
ExceptionTestCase {
public function testException( ) {
$this->setExpectedException('Exception');
}
}
?>
phpunit ExceptionTest
PHPUnit 2.3.0 by Sebastian Bergmann.
F
Time: 0.006798
There was 1 failure:
1) testException(ExceptionTest)
Expected exception Exception
FAILURES!!!
Tests run: 1, Failures: 1, Errors: 0, Incomplete Tests: 0.
PHPUnit2_Extensions_ExceptionTestCase.|
Method
|
Description
|
|---|
PHPUnit2_Extensions_ PerformanceTestCase to test whether the execution of a function or a method call, for instance, exceeds a specified time limit.PHPUnit2_Extensions_ PerformanceTestCase and use its setMaxRunningTime( ) method to set the maximum running time for the test. If the test is not executed within this time limit, it will be counted as a failure.
<?php
require_once 'PHPUnit2/Extensions/PerformanceTestCase.php';
class PerformanceTest extends PHPUnit2_Extensions_
PerformanceTestCase {
public function testPerformance( ) {
$this->setMaxRunningTime(2);
sleep(1);
}
}
?>
PHPUnit2_Extensions_PerformanceTestCase.|
Method
|
Description
|
|---|---|
void setMaxRunningTime(integer $maxRunningTime)
|
Sets the maximum running time for the test to
$maxRunningTime (in seconds).. |
integer getMaxRunningTime( )
|
Returns the maximum running time allowed for the test.
|
public function testSomething( ) {
}
$this->fail( ) in the unimplemented test method does not help either because then the test will be interpreted as a failure. This would be just as wrong as interpreting an unimplemented test as a success.PHPUnit2_ Framework_IncompleteTest is a marker interface for marking an exception that is raised by a test method as the result of the test being incomplete or not currently implemented.
PHPUnit2_Framework_IncompleteTestError is the standard implementation of this interface.SampleTest, that contains one test method, testSomething( ). By raising the PHPUnit2_ Framework_IncompleteTestError exception in the test method, we mark the test as being incomplete.
<?php
require_once 'PHPUnit2/Framework/TestCase.php';
require_once 'PHPUnit2/Framework/IncompleteTestError.php';
class SampleTest extends PHPUnit2_Framework_TestCase {
public function testSomething( ) {
// Optional: Test anything here, if you want.
$this->assertTrue(TRUE, 'This should already work.');
// Stop here and mark this test as incomplete.
// You could use any Exception which implements the
// PHPUnit2_Framework_IncompleteTest interface.
throw new PHPUnit2_Framework_IncompleteTestError(
'This test has not been implemented yet.'
);
}
}
?>
I in the output of the PHPUnit command-line test runner, as shown in the following example:BankAccount class requires methods to get and set the bank account's balance, as well as methods to deposit and withdraw money. It also specifies that the following two conditions must be ensured:BankAccount class before we write the code for the class itself. We use the contract conditions as the basis for the tests and name the test methods accordingly, as shown in Example 10.
<?php
require_once 'PHPUnit2/Framework/TestCase.php';
require_once 'BankAccount.php';
class BankAccountTest extends PHPUnit2_Framework_TestCase {
private $ba;
protected function setUp( ) {
$this->ba = new BankAccount;
}
public function testBalanceIsInitiallyZero( ) {
$this->assertEquals(0, $this->ba->getBalance( ));
}
public function testBalanceCannotBecomeNegative( ) {
try {
$this->ba->withdrawMoney(1);
}
catch (Exception $e) {
return;
}
$this->fail( );
}
public function testBalanceCannotBecomeNegative2( ) {
try {
$this->ba->depositMoney(-1);
}
catch (Exception $e) {
return;
}
$this->fail( );
}
public function testBalanceCannotBecomeNegative3( ) {
try {
$this->ba->setBalance(-1);
}
catch (Exception $e) {
return;
}
$this->fail( );
}
}
?>
testBalanceIsInitiallyZero( ), to pass. In our example, this amounts to implementing the getBalance( ) method of the BankAccount class, as shown in Example 11.
<?php
class BankAccount {
private $balance = 0;
public function getBalance( ) {
return $this->balance;
}
}
?>
BankAccount class (from Example 12) in HTML format generated by the PHPUnit command-line test runner's --coverage-html switch. Executable code lines are black; non-executable code lines are gray. Code lines that are actually executed are highlighted.
setBalance( ), depositMoney( ), and withdrawMoney( ) with legal values in order to achieve complete code coverage. Example 14 shows tests that need to be added to the BankAccountTest test-case class to completely cover the BankAccount class.
<?php
require_once 'PHPUnit2/Framework/TestCase.php';
require_once 'BankAccount.php';
class BankAccountTest extends PHPUnit2_Framework_TestCase {
// …
public function testSetBalance( ) {
$this->ba->setBalance(1);
$this->assertEquals(1, $this->ba->getBalance( ));
}
public function testDepositAndWidthdrawMoney( ) {
$this->ba->depositMoney(1);
$this->assertEquals(1, $this->ba->getBalance( ));
$this->ba->withdrawMoney(1);
$this->assertEquals(0, $this->ba->getBalance( ));
}
}
?>
BankAccountPHPUnit2_ Extensions_TestSetup decorator helps, but not using the database for the purposes of the tests at all is even better.
Database object—an implementor of the
IDatabase interface. Then, you can create a stub implementation of IDatabase and use it for your tests. You can even create an option for running the tests with the stub database or the real database, so you can use your tests for both local testing during development and integration testing with the real database.
Observer:
class ObserverTest extends PHPUnit2_Framework_TestCase
implements Observer{
}
Observer method, update( ), to check that it is called when the state of the observed Subject object changes:
public $wasCalled = FALSE;
public function update(Subject $subject) {
$this->wasCalled = TRUE;
}
Subject object and attach the test object to it as an observer. When the state of the Subject changes—for instance, by calling its doSomething( ) method—the Subject object has to call the update( ) method on all objects that are registered as observers. We use the $wasCalled instance variable that is set by our implementation of update( ) to check whether the Subject object does what it is supposed to do:
public function testUpdate( ) {
$subject = new Subject;
$subject->attach($this);
$subject->doSomething( );
$this->assertTrue($this->wasCalled);
}
Subject object instead of relying on a global instance. Stubbing encourages this style of design. It reduces the coupling between objects and improves reuse.testBalanceIsInitiallyZero( ) becomes "Balance is initially zero." If there are several test methods whose names differ only by a suffix of one or more digits, such as testBalanceCannotBecomeNegative( ) and testBalanceCannotBecomeNegative2( ), the sentence "Balance cannot become negative" will appear only once, assuming that all of these tests succeed.BankAccount - Balance is initially zero - Balance cannot become negative
testdox-html BankAccountTest.htm.testBalanceIsInitiallyZero( ) becomes "Balance is initially zero." If there are several test methods whose names differ only by a suffix of one or more digits, such as testBalanceCannotBecomeNegative( ) and testBalanceCannotBecomeNegative2( ), the sentence "Balance cannot become negative" will appear only once, assuming that all of these tests succeed.BankAccount - Balance is initially zero - Balance cannot become negative
testdox-html BankAccountTest.htm.