Last week, we introduced you to setting up a unit testing framework. This week, you will learn about running multiple tests simultaneously, creating more informative error messages, and more. This article, the second of three parts, is excerpted from chapter 6 of the book Advanced PHP Programming, written by George Schlossnagle (Sams; ISBN: 0672325616).
When you execute PHPUnit_TextUI_TestRunner::run(), that function creates a PHPUnit_Framework_TestResult object in which the results of the tests will be stored, and it attaches to it alistener, which implements the interface PHPUnit_Framework_TestListener. This listener handles generating any output or performing any notifications based on the test results.
To help you make sense of this, here is a simplified version of PHPUnit_TextUI_TestRunner::run(), myTestRunner(). MyTestRunner() executes the tests identically to TextUI, but it lacks the timing support you may have noticed in the earlier output examples:
require_once "PHPUnit/TextUI/ResultPrinter.php";
require_once "PHPUnit/Framework/TestResult.php";
function myTestRunner($suite)
{
$result = new PHPUnit_Framework_TestResult;
$textPrinter = new PHPUnit_TextUI_ResultPrinter;
$result->addListener($textPrinter);
$suite->run($result);
$textPrinter->printResult($result);
}
PHPUnit_TextUI_ResultPrinter is a listener that handles generating all the output we've seen before. You can add additional listeners to your tests as well. This is useful if you want to bundle in additional reporting other than simply displaying text. In a large API, you might want to alert a developer by email if a component belonging to that developer starts failing its unit tests (because that developer might not be the one running the test). You can write a listener that provides this service:
<?php
require_once "PHPUnit/Framework/TestListener.php";
class EmailAddressListener implements
PHPUnit_Framework_TestListener {
public $owner = "develepors@example.foo";
public $message = '';
public function addError(PHPUnit_Framework_Test
$test, Exception $e)
{
$this->message .= "Error in
".$test->getName()."\n";
$this->message .= "Error message:
".$e->getMessage()."\n";
}
public function addFailure(PHPUnit_Framework_Test
$test,
PHPUnit_Framework_AssertionFailedError
$e)
{
$this->message .= "Failure in
".$test->getName()."\n";
$this->message .= "Error message:
".$e->getMessage()."\n";
}
public function startTest(PHPUnit_Framework_Test
$test)
{
$this->message .= "Beginning of test
".$test->getName()."\n";
}
public function endTest(PHPUnit_Framework_Test
$test)
{
if($this->message) {
$owner =
isset($test->owner)?$test->owner:$this->owner;
$date = strftime("%D %H:%M:%S");
mail($owner, "Test Failed at $date",
$this->message);
}
}
}
?>
Remember that because EmailAddressListener implements PHPUnit_Framework_TestListener (and does not extend it), EmailAddressListener must implement all the methods defined in PHPUnit_Framework_TestListener, with the same prototypes.
This listener works by accumulating all the error messages that occur in a test. Then, when the test ends, endTest() is called and the message is dispatched. If the test in question has an owner attribute, that address is used; otherwise, it falls back to developers@example.foo.
To enable support for this listener in myTestRunner(), all you need to do is add it with addListener():
function myTestRunner($suite)
{
$result = new PHPUnit_Framework_TestResult;
$textPrinter = new PHPUnit_TextUI_ResultPrinter;
$result->addListener($textPrinter);
$result->addListener(new EmailAddressListener);
$suite->run($result);
$textPrinter->printResult($result);
}
Please check back next week for the conclusion of this article.