Prev Next

Chapter 20. PHPUnit's Implementation

The implementation of PHPUnit is a bit unusual, using techniques that are difficult to maintain in ordinary application code. Understanding how PHPUnit runs your tests can help you write them.

A single test is represented by a PHPUnit_Framework_Test object and requires a PHPUnit_Framework_TestResult object to be run. The PHPUnit_Framework_TestResult object is passed to the PHPUnit_Framework_Test object's run() method, which runs the actual test method and reports any exceptions to the PHPUnit_Framework_TestResult object. This is an idiom from the Smalltalk world called Collecting Parameter. It suggests that when you need to collect results over several methods (in our case the result of the serveral invocations of the run() method for the various tests), you should add a parameter to the method and pass an object that will collect the results for you. See the article "JUnit: A Cook's Tour" by Erich Gamma and Kent Beck [GammaBeck1999] and "Smalltalk Best Practice Patterns" by Kent Beck [Beck1997].

To further understand how PHPUnit runs your tests, consider the test-case class in Example 20.1.

Example 20.1: The EmptyTest class

<?php
require_once 'PHPUnit/Framework.php';

class EmptyTest extends PHPUnit_Framework_TestCase
{
private $emptyArray = array();

public function testSize()
{
$this->assertEquals(0, sizeof($this->emptyArray));
}

public function testIsEmpty()
{
$this->assertTrue(empty($this->emptyArray));
}
}
?>

When the test is run, the first thing PHPUnit does is convert the test class into a PHPUnit_Framework_Test object -- here, a PHPUnit_Framework_TestSuite containing two instances of EmptyTest, as shown in Figure 20.1.

Figure 20.1. Tests about to be run

Tests about to be run

When the PHPUnit_Framework_TestSuite is run, it runs each of the EmptyTests in turn. Each runs its own setUp() method, creating a fresh $emptyArray for each test, as shown in Figure 20.2. This way, if one test modifies the array, the other test will not be affected. Even changes to global and super-global variables (such as $GLOBALS) do not affect other tests.

Note

The implementation of the backup and restore operations for the global and super-global variables uses serialize() and unserialize(). Objects of some classes that are provided by PHP itself, such as PDO, cannot be serialized and the backup operation will break when such an object is stored in the $GLOBALS array, for instance.

The backup and restore operations for the global and super-global variables can be disabled like this

class MyTest extends PHPUnit_Framework_TestCase
{
    protected $backupGlobals = FALSE;

    // ...
}

Please note that setting the $backupGlobals attribute inside the setUp() method, for instance, has no effect.

Figure 20.2. Tests after running, each with its own fixture

Tests after running, each with its own fixture

In short, one test-case class results in a two-level tree of objects when the tests are run. Each test method works with its own copy of the objects created by setUp(). The result is tests that can run completely independently.

To run the test method itself, PHPUnit uses reflection to find the method name in the instance variable $name and invokes it. This is another idiom, called Pluggable Selector, that is commonly used in the Smalltalk world. Using a Pluggable Selector makes the writing of tests simpler, but there is a tradeoff: you cannot look at the code to decide whether a method is invoked, you have to look at the data values at runtime.

Prev Next
1. Automating Tests
2. PHPUnit's Goals
3. Installing PHPUnit
4. Writing Tests for PHPUnit
Data Providers
Testing Exceptions
Testing PHP Errors
5. The Command-Line Test Runner
6. Fixtures
More setUp() than tearDown()
Variations
Sharing Fixture
7. Organizing Test Suites
Suite-Level Setup
8. TestCase Extensions
Testing Output
Testing Performance
9. Database Testing
Datasets
Flat XML Data Set
XML Data Set
Operations
Database Testing Best Practices
10. Incomplete and Skipped Tests
Incomplete Tests
Skipping Tests
11. Mock Objects
Self-Shunting
Stubs
12. Testing Practices
During Development
During Debugging
13. Test-First Programming
BankAccount Example
14. Code Coverage Analysis
Specifying Covered Methods
Ignoring Code Blocks
Including and Excluding Files
15. Other Uses for Tests
Agile Documentation
Cross-Team Tests
16. Logging
XML Format
Code Coverage (XML)
JavaScript Object Notation (JSON)
Test Anything Protocol (TAP)
GraphViz Markup
Test Database
17. Skeleton Generator
Annotations
18. PHPUnit and Selenium
Selenium RC
PHPUnit_Extensions_SeleniumTestCase
19. Continuous Integration
CruiseControl
phpUnderControl
Apache Maven
20. PHPUnit's Implementation
21. PHPUnit API
Overview
PHPUnit_Framework_Assert
PHPUnit_Framework_Test
PHPUnit_Framework_TestCase
PHPUnit_Framework_TestSuite
PHPUnit_Framework_TestResult
Package Structure
22. Extending PHPUnit
Subclass PHPUnit_Framework_TestCase
Assert Classes
Subclass PHPUnit_Extensions_TestDecorator
Implement PHPUnit_Framework_Test
Subclass PHPUnit_Framework_TestResult
Implement PHPUnit_Framework_TestListener
New Test Runner
A. Assertions
B. The XML Configuration File
Test Suite
Groups
Including and Excluding Files for Code Coverage
Logging
PMD Rules
Setting PHP INI settings and Global Variables
C. PHPUnit for PHP 4
D. Index
E. Bibliography
F. Copyright