Identifing Slow PHPUnit Tests

Note: this is for PHPUnit 3.5 or less. We’re restricted by a platform limitation from upgrading etc etc.

I wanted to find out which unit tests were taking a long time to run and to try and get our build time down a little. I believe that unit tests should not take longer then 2 seconds to run. Not the entire suite, that’s silly. Each test. Ideally, it’d be less then a second but, hey we don’t live in a perfect world.

So I implemented a Test Listener. The listener checks the timing provided by PHP Unit against constants set and can fail our CI Build if you check in a slow test. Enough rambling, here’s the code.

In your phpunit.xml add the following:

  <listeners>
    <listener class="TestTimesListener" file="./TestTimesListener.php" />
  </listeners>

Create the file TestTimesListener.php right next to it, or in the directory of your choice.


<?php
class TestTimesListener implements PHPUnit_Framework_TestListener
{
    /**
     * Anything above this threshold is deemed slow
     * @var float $seconds
     */
    const MAX_EXECUTION_TIME = 0.2;

    /**
     * Absolute max time we should allow a unit test to run.
     * @var float $seconds
     */
    const FAIL_IF_LONGER_THAN = 2.0;

    /**
     * Helper variables for assinting with naming 
     * @var string
     */
    private $_currentSuite;
    private $_fullTestName;

    public function startTest(PHPUnit_Framework_Test $test)
    {
        $this->_fullTestName = $this->_currentSuite->getName() . "::" . $test->getName();
    }

    public function endTest(PHPUnit_Framework_Test $test, $time)
    {
        if ($time > self::MAX_EXECUTION_TIME) {
            printf(
                PHP_EOL . "%s took %f seconds" . PHP_EOL, 
                $this->_fullTestName,
                $time
            );
            
            if ($time >= self::FAIL_IF_LONGER_THAN) {
                $test->fail(
                    sprintf(
                        "%s is too slow, it takes %f seconds", 
                        $this->_fullTestName,
                        $time
                    )
                );
            }
        }
    }

    public function addError(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
    }
    
    public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time)
    {
    }
    
    public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
    }
    
    public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
    }
    
    public function startTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
        $this->_currentSuite = $suite;
    }

    public function endTestSuite(PHPUnit_Framework_TestSuite $suite) 
    {
    }
}

Now, set those thresholds to something nice and low and run your test suites!

Leave a comment