Add PHPUnit Listeners to Watch for Long Running Tests

This post is more than 18 months old. Since technology changes to rapidly, this content may be out of date (but that's not always the case). Please remember to verify any technical or programming information with the current release.

One of the under-utilized features of PHPUnit probably is the listeners interface. You can see the configuration options here: php.net/appendixes.configuration.html. So, I decided that I want to use this to know if a Unit Test takes longer than 2 seconds to run. That’s super over-kill in my opinion, but that’s my hard limit. If it takes longer than 2 seconds to run, something is wrong! So, I added the following to my configuration:

excerpt from phpunit.xml

<listeners>
  <listener class="Application_Test_TestTimesListener" file="scripts/TestTimesLimitListener.php"></listener>
</listeners>

This simply says to invoke the listener from TestTimesLimitListener.php - which class name is Application_test_TestTimesListener. Here is the code:

/**
 * Listener Class for test times
 * 
 * @package Tests
 */
class Application_Test_TestTimesListener implements PHPUnit_Framework_TestListener
{
  /**
   * Number of seconds that this test can run
   * @var integer
   */
  const TEST_LIMIT_TIME = 2;
    
  /**
   * called when test is ended - determines if it was long and prints
   * @param PHUnit_Framework_Test $test
   * @param float $length the length of time for the test
   */
  public function endTest(PHPUnit_Framework_Test $test, $length)
  {
    if ($length > self::TEST_LIMIT_TIME) {
      $this->_printError($test->getName() . " ran for {$length} seconds");
    }
  }
    
  /**
   * Used to print error in error colors
   * @param string $error
   */
  protected function _printError($error)
  {
    print "\n\033[41m" . $error . "\033[0m\n";
  }
    
  /**
   * Required for Interface
   * (non-PHPdoc)
   * @see PHPUnit_Framework_TestListener::startTest()
   */
  public function startTest(PHPUnit_Framework_Test $test) {}

  /**
   * Required for Interface
   * (non-PHPdoc)
   * @see PHPUnit_Framework_TestListener::addError()
   */
  public function addError(PHPUnit_Framework_Test $test, Exception $e, $time) {}

  /**
   * Required for Interface
   * (non-PHPdoc)
   * @see PHPUnit_Framework_TestListener::addFailure()
   */
  public function addFailure(
    PHPUnit_Framework_Test $test, 
    PHPUnit_Framework_AssertionFailedError $e, 
    $time
  ) {}

  /**
   * Required for Interface
   * (non-PHPdoc)
   * @see PHPUnit_Framework_TestListener::addError()
   */
  public function addIncompleteTest(
    PHPUnit_Framework_Test $test, 
    Exception $e, 
    $time
  ) {}

  /**
   * Required for Interface
   * (non-PHPdoc)
   * @see PHPUnit_Framework_TestListener::addSkippedTest()
   */
  public function addSkippedTest(
    PHPUnit_Framework_Test $test, 
    Exception $e, 
    $time
  ) {}
    
  /**
   * Required for Interface
   * (non-PHPdoc)
   * @see PHPUnit_Framework_TestListener::startTestSuite()
   */
  public function startTestSuite(PHPUnit_Framework_TestSuite $suite) {}
    
  /**
   * Required for Interface
   * (non-PHPdoc)
   * @see PHPUnit_Framework_TestListener::endTestSuite()
   */
  public function endTestSuite(PHPUnit_Framework_TestSuite $suite) {}	
}

First of all, the class must implement the PHPUnit_Framework_TestListener interface. That’s why at the end you’ll see all the extra empty methods piled on. Let’s dive into the code:

Define a constant of the number of whole seconds. Next, define the endTest method which will receive a float of how many seconds/milliseconds that test took to run. If it takes longer than our limit, print an error using the test name. The _printError() method uses some control characters to change the font color red.

There you go! Now, during your normal execution of unit tests, you may see a few warnings for long running tests in red. Go and correct those to keep your tests running smooth and fast.

Please note: a while ago, I ran into a blog entry about this. It wasn’t clear to me at the time, so I didn’t book mark it. However, I wanted to mention that this isn’t 100% only my work. Credit to where credit is due.

Return to All Posts

or use RSS