├── .gitignore ├── tests ├── ExceptionalTest.php └── SomeSlowTest.php ├── composer.json ├── LICENSE ├── phpunit.xml.dist ├── .github └── workflows │ └── integrate.yaml ├── CHANGELOG.md ├── UPGRADE.md ├── src └── SpeedTrap.php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /phpunit.xml 2 | /vendor 3 | /composer.lock 4 | -------------------------------------------------------------------------------- /tests/ExceptionalTest.php: -------------------------------------------------------------------------------- 1 | expectException('InvalidArgumentException'); 13 | $this->expectExceptionMessage('CODE1'); 14 | throw new \InvalidArgumentException('CODE1'); 15 | } 16 | 17 | public function testSkippedTest() 18 | { 19 | $this->markTestSkipped('Skipped tests do not cause Exceptions in SpeedTrap extension'); 20 | } 21 | 22 | public function testIncompleteTest() 23 | { 24 | $this->markTestIncomplete('Incomplete tests do not cause Exceptions in SpeedTrap extension'); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "johnkary/phpunit-speedtrap", 3 | "type": "library", 4 | "description": "Find and report on slow tests in your PHPUnit test suite", 5 | "keywords": [ 6 | "PHPUnit", 7 | "extension", 8 | "hook", 9 | "listener", 10 | "slow", 11 | "profile" 12 | ], 13 | "homepage": "https://github.com/johnkary/phpunit-speedtrap", 14 | "license": "MIT", 15 | "authors": [ 16 | { 17 | "name": "John Kary", 18 | "email": "john@johnkary.net" 19 | } 20 | ], 21 | "require": { 22 | "php": ">=7.2", 23 | "phpunit/phpunit": "^8.0 || ^9.0" 24 | }, 25 | "extra": { 26 | "branch-alias": { 27 | "dev-master": "5.0-dev" 28 | } 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "JohnKary\\PHPUnit\\Extension\\": "src/" 33 | } 34 | }, 35 | "autoload-dev": { 36 | "psr-4": { 37 | "JohnKary\\PHPUnit\\Extension\\Tests\\": "tests/" 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 John Kary 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | src 12 | 13 | 14 | 15 | 16 | 17 | tests 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 500 27 | 28 | 29 | 5 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /.github/workflows/integrate.yaml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/actions 2 | 3 | name: "Integrate" 4 | 5 | on: 6 | pull_request: null 7 | push: 8 | branches: 9 | - "master" 10 | 11 | jobs: 12 | tests: 13 | name: "Tests" 14 | 15 | runs-on: "ubuntu-latest" 16 | 17 | strategy: 18 | fail-fast: false 19 | 20 | matrix: 21 | include: 22 | - php-version: "7.2" 23 | phpunit-version: "8.*" 24 | 25 | - php-version: "7.3" 26 | phpunit-version: "8.*" 27 | 28 | - php-version: "7.3" 29 | phpunit-version: "9.*" 30 | 31 | - php-version: "7.4" 32 | phpunit-version: "8.*" 33 | 34 | - php-version: "7.4" 35 | phpunit-version: "9.*" 36 | 37 | - php-version: "8.0" 38 | phpunit-version: "8.*" 39 | 40 | - php-version: "8.0" 41 | phpunit-version: "9.*" 42 | 43 | - php-version: "8.1" 44 | phpunit-version: "8.*" 45 | 46 | - php-version: "8.1" 47 | phpunit-version: "9.*" 48 | 49 | steps: 50 | - name: "Checkout" 51 | uses: "actions/checkout@v2" 52 | 53 | - name: "Set up PHP" 54 | uses: "shivammathur/setup-php@v2" 55 | with: 56 | coverage: "none" 57 | php-version: "${{ matrix.php-version }}" 58 | 59 | - name: "Set up problem matchers for phpunit/phpunit" 60 | run: "echo \"::add-matcher::${{ runner.tool_cache }}/phpunit.json\"" 61 | 62 | - name: "Determine composer cache directory" 63 | run: "echo \"COMPOSER_CACHE_DIR=$(composer config cache-dir)\" >> $GITHUB_ENV" 64 | 65 | - name: "Cache dependencies installed with composer" 66 | uses: "actions/cache@v2" 67 | with: 68 | path: "${{ env.COMPOSER_CACHE_DIR }}" 69 | key: "php-${{ matrix.php-version }}-composer-${{ matrix.phpunit-version }}" 70 | restore-keys: "php-${{ matrix.php-version }}-composer-" 71 | 72 | - name: "Require phpunit/phpunit ${{ matrix.phpunit-version }}" 73 | run: "composer require phpunit/phpunit:${{ matrix.phpunit-version }}" 74 | 75 | - name: "Run tests with phpunit/phpunit" 76 | run: "vendor/bin/phpunit" 77 | -------------------------------------------------------------------------------- /tests/SomeSlowTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 13 | } 14 | 15 | public function testSlowTests() 16 | { 17 | $this->extendTime(300); 18 | 19 | $this->assertTrue(true); 20 | } 21 | 22 | public function testAnotherSlowTests() 23 | { 24 | $this->extendTime(500); 25 | 26 | $this->assertTrue(true); 27 | } 28 | 29 | public function testLongEndToEndTest() 30 | { 31 | $this->extendTime(500); 32 | 33 | $this->assertTrue(true); 34 | } 35 | 36 | public function testSlowTestsOverOneSecond() 37 | { 38 | $this->extendTime(1300); 39 | 40 | $this->assertTrue(true); 41 | } 42 | 43 | /** 44 | * @dataProvider provideTime 45 | */ 46 | public function testWithDataProvider(int $time) 47 | { 48 | $this->extendTime($time); 49 | 50 | $this->assertTrue(true); 51 | } 52 | public function provideTime() 53 | { 54 | return [ 55 | 'Rock' => [800], 56 | 'Chalk' => [700], 57 | 'Jayhawk' => [600], 58 | ]; 59 | } 60 | 61 | /** 62 | * This test's runtime would normally be under the suite's threshold, but 63 | * this annotation sets a lower threshold, causing it to be considered slow 64 | * and reported on in the test output. 65 | * 66 | * @slowThreshold 5 67 | */ 68 | public function testCanSetLowerSlowThreshold() 69 | { 70 | $this->extendTime(10); 71 | $this->assertTrue(true); 72 | } 73 | 74 | /** 75 | * This test's runtime would normally be over the suite's threshold, but 76 | * this annotation sets a higher threshold causing it not to be 77 | * considered slow and not reported in the test output. 78 | * 79 | * @slowThreshold 50000 80 | */ 81 | public function testCanSetHigherSlowThreshold() 82 | { 83 | $this->extendTime(600); 84 | $this->assertTrue(true); 85 | } 86 | 87 | /** 88 | * This test's runtime would normally be over the suite's threshold, but 89 | * this annotation disables threshold checks causing it not to be 90 | * considered slow and not reported in the test output. 91 | * 92 | * @slowThreshold 0 93 | */ 94 | public function testCanDisableSlowThreshold() 95 | { 96 | $this->extendTime(600); 97 | $this->assertTrue(true); 98 | } 99 | 100 | /** 101 | * @param int $ms Number of additional microseconds to execute code 102 | */ 103 | private function extendTime(int $ms) 104 | { 105 | usleep($ms * 1000); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | CHANGELOG 2 | ================= 3 | 4 | View diff for a specific commit: 5 | https://github.com/johnkary/phpunit-speedtrap/commit/XXX where XXX is the commit hash 6 | 7 | View diff between two versions: 8 | https://github.com/johnkary/phpunit-speedtrap/compare/v4.0.0...v5.0.0 9 | 10 | ## 5.0 (xxxx-xx-xx) 11 | 12 | Version 5.0 is the largest change since v1.0. It now uses PHPUnit's Extension 13 | and Hook systems, which have more restrictions on what an Extension is allowed 14 | to do. 15 | 16 | Changes are required if you have extended SpeedTrapListener. See 17 | [UPGRADE.md](UPGRADE.md) for upgrading your subclass to support 5.0. 18 | 19 | * SpeedTrap now requires PHPUnit 8+ and PHP 7.2+, 20 | * Moved namespace from `JohnKary\PHPUnit\Listener\SpeedTrapListener` to `JohnKary\PHPUnit\Extension\SpeedTrap` 21 | * `phpunit.xml` requires registering using element instead of element. See README. 22 | * Removed option `stopOnSlow` because Extensions can no longer manipulate the Test Runner 23 | 24 | ## 4.0.1 (2022-10-16) 25 | 26 | * README documents working with Symfony Framework `simple-phpunit` 27 | 28 | ## 4.0.0 (2021-05-03) 29 | 30 | * Changelog ([`v3.3.0...v.4.0.0`](https://github.com/johnkary/phpunit-speedtrap/compare/v3.3.0...v4.0.0)) 31 | * [PR #81](https://github.com/johnkary/phpunit-speedtrap/pull/81) Reformat slow test case output for compatibility with PHPUnit --filter option 32 | * [PR #82](https://github.com/johnkary/phpunit-speedtrap/pull/82) New option `stopOnSlow` stops execution upon first slow test. Default: false. 33 | * [PR #84](https://github.com/johnkary/phpunit-speedtrap/pull/84) New annotation option `@slowThreshold 0` disables checks for individual tests. 34 | 35 | ## 3.3.0 (2020-12-18) 36 | 37 | Version 3.3 adds supports for PHPUnit 9.5+, and a way to enable or disable the SpeedTrap listener using environment variables. 38 | 39 | * [PR #73](https://github.com/johnkary/phpunit-speedtrap/pull/73) Compatibility with PHPUnit 9.5 40 | * [PR #66](https://github.com/johnkary/phpunit-speedtrap/pull/66) Environment variable PHPUNIT_SPEEDTRAP="disabled" can disable profiling 41 | 42 | ## 3.2.0 (2020-02-12) 43 | 44 | Version 3.2 introduces supports for PHPUnit 9.0+. 45 | If your use of SpeedTrap depends on specific text output from SpeedTrap slowness 46 | report, see below wording changes that may require updating your implementation. 47 | 48 | * [PR #57](https://github.com/johnkary/phpunit-speedtrap/pull/57) Wording change to slowness report in renderHeader() 49 | 50 | ## 3.1.0 (2019-02-23) 51 | 52 | Version 3.1 introduces support for PHPUnit 8.0+. 53 | 54 | ## 3.0.0 (2018-02-24) 55 | 56 | Version 3.0 introduces support for PHPUnit 7.0+ and PHP 7.1+. 57 | 58 | Changes may be required if you have extended SpeedTrapListener. See 59 | [UPGRADE.md](UPGRADE.md) for upgrading your subclass to support 3.0. 60 | 61 | * [PR #41](https://github.com/johnkary/phpunit-speedtrap/pull/41) Make compatible with phpunit 7.x 62 | 63 | ## 2.0.0 (2017-12-06) 64 | 65 | Version 2.0 introduces support for PHPUnit 6.0+, PHP 7.0+ and PSR-4 autoloading. 66 | 67 | Changes are required if you have extended SpeedTrapListener. See 68 | [UPGRADE.md](UPGRADE.md) for upgrading your subclass to support 2.0. 69 | 70 | * [PR #26](https://github.com/johnkary/phpunit-speedtrap/pull/26) Support PHPUnit 6.0 71 | * [PR #30](https://github.com/johnkary/phpunit-speedtrap/pull/30) Add PHP 7 features (includes backwards compatibility breaks, see UPGRADE.md) 72 | * [PR #31](https://github.com/johnkary/phpunit-speedtrap/pull/31) Support PSR-4 autoloading 73 | * [PR #39](https://github.com/johnkary/phpunit-speedtrap/pull/39) SpeedTrapListener extends PHPUnit BaseTestListener 74 | -------------------------------------------------------------------------------- /UPGRADE.md: -------------------------------------------------------------------------------- 1 | UPGRADE FROM 4.x to 5.0 2 | ======================= 3 | 4 | This library's v4 core file `JohnKary\PHPUnit\Listener\SpeedTrapListener` has been 5 | replaced by `JohnKary\PHPUnit\Extension\SpeedTrap` in v5. This change reflects 6 | switching from PHPUnit's Listener system to its Hook system. 7 | 8 | The `SpeedTrap` Extension must be registered differently in `phpunit.xml`: 9 | 10 | ```xml 11 | 12 | ... 13 | - 14 | - 15 | - 16 | + 17 | + 18 | + 19 | + 20 | + 21 | + 500 22 | + 23 | + 24 | + 5 25 | + 26 | + 27 | + 28 | + 29 | + 30 | 31 | ``` 32 | 33 | Depending on how you install phpunit-speedtrap, you may need to dump the 34 | Composer autoloader to find the new autoload location: `composer dump-autoload` 35 | 36 | If you have extended the old `JohnKary\PHPUnit\Listener\SpeedTrapListener`, you 37 | must extend the new `JohnKary\PHPUnit\Extension\SpeedTrap`. There are various 38 | method name changes that may affect your custom subclass. See [PR #83](https://github.com/johnkary/phpunit-speedtrap/pull/83) 39 | for many of the new class has changed. 40 | 41 | If you programmatically parse the slowness report text visible when running 42 | `vendor/bin/phpunit`, there have been some text formatting changes in the output: 43 | 44 | * The header text has changed 45 | * The footer text has changed 46 | * The slow test output has changed 47 | * Slowness execution time now displays in seconds instead of milliseconds 48 | 49 | UPGRADE FROM 3.x to 4.0 50 | ======================= 51 | 52 | ### Slowness report changes formatting of slow class names 53 | 54 | Prior to 4.0 the slowness report displayed the qualified class name in a 55 | human-readable format as normally seen in code: 56 | 57 | 1. 800ms to run JohnKary\PHPUnit\Listener\Tests\SomeSlowTest:testWithDataProvider with data set "Rock" 58 | 59 | After 4.0 the slowness report displays class names in a format ready to be 60 | used with PHPUnit's [--filter option](https://phpunit.readthedocs.io/en/9.5/textui.html?highlight=filter) 61 | by adding slashes to the namespace delimiter and adding a colon between the 62 | class and method name: 63 | 64 | 1. 800ms to run JohnKary\\PHPUnit\\Listener\\Tests\\SomeSlowTest::testWithDataProvider with data set "Rock" 65 | 66 | An individual slow test case can now be re-run by copying and pasting the output 67 | into a new command: 68 | 69 | vendor/bin/phpunit --filter 'JohnKary\\PHPUnit\\Listener\\Tests\\SomeSlowTest::testWithDataProvider with data set "Rock"' 70 | 71 | Note that PHPUnit uses single quotes for the `--filter` option value. See the 72 | [--filter option documentation](https://phpunit.readthedocs.io/en/9.5/textui.html?highlight=filter) 73 | for all supported matching patterns. 74 | 75 | UPGRADE FROM 2.x to 3.0 76 | ======================= 77 | 78 | ### `JohnKary\PHPUnit\Listener\SpeedTrapListener` subclasses must ensure method signatures match PHPUnit TestListenerDefaultImplementation 79 | 80 | SpeedTrapListener was upgraded to support PHPUnit 7.0, which introduced a 81 | new trait `TestListenerDefaultImplementation` containing a few new scalar type 82 | hints and void return hints. SpeedTrapListener subclasses overriding any 83 | of the below methods will require updating the new method signatures: 84 | 85 | | Old signature | New signature | 86 | | -------- | --- | 87 | | `public function endTest(Test $test, $time)` | `public function endTest(Test $test, float $time): void` 88 | | `public function startTestSuite(TestSuite $suite)` | `public function startTestSuite(TestSuite $suite): void` 89 | | `public function endTestSuite(TestSuite $suite)` | `public function endTestSuite(TestSuite $suite): void` 90 | 91 | 92 | UPGRADE FROM 1.x to 2.0 93 | ======================= 94 | 95 | ### `JohnKary\PHPUnit\Listener\SpeedTrapListener` subclasses must implement scalar type hints 96 | 97 | SpeedTrapListener was upgraded to support PHP 7 scalar type hints. Any 98 | subclass will need to update the overridden function signature: 99 | 100 | * Declare strict types at the top of your subclass: `declare(strict_types=1);` 101 | * Update method signatures: 102 | 103 | | Old signature | New signature | 104 | | -------- | --- | 105 | | `protected function isSlow($time, $slowThreshold)` | `protected function isSlow(int $time, int $slowThreshold) : bool` 106 | | `protected function addSlowTest(TestCase $test, $time)` | `protected function addSlowTest(TestCase $test, int $time)` 107 | | `protected function hasSlowTests()` | `protected function hasSlowTests() : bool` 108 | | `protected function toMilliseconds($time)` | `protected function toMilliseconds(float $time) : int` 109 | | `protected function makeLabel(TestCase $test)` | `protected function makeLabel(TestCase $test) : string` 110 | | `protected function getReportLength()` | `protected function getReportLength() : int` 111 | | `protected function getHiddenCount()` | `protected function getHiddenCount() : int` 112 | | `protected function getSlowThreshold(TestCase $test)` | `protected function getSlowThreshold(TestCase $test) : int` 113 | -------------------------------------------------------------------------------- /src/SpeedTrap.php: -------------------------------------------------------------------------------- 1 | Printable label describing the test 54 | * Values (int) => Test execution time, in milliseconds 55 | */ 56 | protected $slow = []; 57 | 58 | public function __construct(array $options = []) 59 | { 60 | $this->enabled = getenv('PHPUNIT_SPEEDTRAP') === 'disabled' ? false : true; 61 | 62 | $this->loadOptions($options); 63 | } 64 | 65 | /** 66 | * A test successfully ended. 67 | * 68 | * @param string $test 69 | * @param float $time 70 | */ 71 | public function executeAfterSuccessfulTest(string $test, float $time): void 72 | { 73 | if (!$this->enabled) return; 74 | 75 | $timeMS = $this->toMilliseconds($time); 76 | $threshold = $this->getSlowThreshold($test); 77 | 78 | if ($this->isSlow($timeMS, $threshold)) { 79 | $this->addSlowTest($test, $timeMS); 80 | } 81 | } 82 | 83 | /** 84 | * A test suite started. 85 | */ 86 | public function executeBeforeFirstTest(): void 87 | { 88 | if (!$this->enabled) return; 89 | 90 | $this->suites++; 91 | } 92 | 93 | /** 94 | * A test suite ended. 95 | */ 96 | public function executeAfterLastTest(): void 97 | { 98 | if (!$this->enabled) return; 99 | 100 | $this->suites--; 101 | 102 | if (0 === $this->suites && $this->hasSlowTests()) { 103 | arsort($this->slow); // Sort longest running tests to the top 104 | 105 | $this->renderHeader(); 106 | $this->renderBody(); 107 | $this->renderFooter(); 108 | } 109 | } 110 | 111 | /** 112 | * Whether the given test execution time is considered slow. 113 | * 114 | * @param int $time Test execution time in milliseconds 115 | * @param int $slowThreshold Test execution time at which a test should be considered slow, in milliseconds 116 | */ 117 | protected function isSlow(int $time, int $slowThreshold): bool 118 | { 119 | return $slowThreshold && $time >= $slowThreshold; 120 | } 121 | 122 | /** 123 | * Stores a test as slow. 124 | * 125 | * @param int $time Test execution time that was considered slow, in milliseconds 126 | */ 127 | protected function addSlowTest(string $test, int $time): void 128 | { 129 | $label = $this->makeLabel($test); 130 | 131 | $this->slow[$label] = $time; 132 | } 133 | 134 | /** 135 | * Whether at least one test has been considered slow. 136 | */ 137 | protected function hasSlowTests(): bool 138 | { 139 | return !empty($this->slow); 140 | } 141 | 142 | /** 143 | * Convert PHPUnit's reported test time (microseconds) to milliseconds. 144 | */ 145 | protected function toMilliseconds(float $time): int 146 | { 147 | return (int) round($time * 1000); 148 | } 149 | 150 | /** 151 | * Label describing a slow test case. Formatted to support copy/paste with 152 | * PHPUnit's --filter CLI option: 153 | * 154 | * vendor/bin/phpunit --filter 'JohnKary\\PHPUnit\\Extension\\Tests\\SomeSlowTest::testWithDataProvider with data set "Rock"' 155 | */ 156 | protected function makeLabel(string $test): string 157 | { 158 | list($class, $testName) = explode('::', $test); 159 | 160 | // Remove argument list from end of string that is appended 161 | // by default \PHPUnit\Framework\TestCase->toString() so slowness report 162 | // output compatible with phpunit --filter flag 163 | $testName = preg_replace('/\s\(.*\)$/', '', $testName); 164 | 165 | return sprintf('%s::%s', addslashes($class), $testName); 166 | } 167 | 168 | /** 169 | * Calculate number of tests to include in slowness report. 170 | */ 171 | protected function getReportLength(): int 172 | { 173 | return min(count($this->slow), $this->reportLength); 174 | } 175 | 176 | /** 177 | * Calculate number of slow tests to be hidden from the slowness report 178 | * due to list length. 179 | */ 180 | protected function getHiddenCount(): int 181 | { 182 | $total = count($this->slow); 183 | $showing = $this->getReportLength(); 184 | 185 | $hidden = 0; 186 | if ($total > $showing) { 187 | $hidden = $total - $showing; 188 | } 189 | 190 | return $hidden; 191 | } 192 | 193 | /** 194 | * Renders slowness report header. 195 | */ 196 | protected function renderHeader(): void 197 | { 198 | echo sprintf("\n\nThe following tests were detected as slow (>%sms)\n", $this->slowThreshold); 199 | } 200 | 201 | /** 202 | * Renders slowness report body. 203 | */ 204 | protected function renderBody(): void 205 | { 206 | $slowTests = $this->slow; 207 | 208 | $length = $this->getReportLength(); 209 | for ($i = 1; $i <= $length; ++$i) { 210 | $label = key($slowTests); 211 | $time = array_shift($slowTests); 212 | $seconds = $time / 1000; 213 | 214 | echo sprintf(" %s) %.3fs to run %s\n", $i, $seconds, $label); 215 | } 216 | } 217 | 218 | /** 219 | * Renders slowness report footer. 220 | */ 221 | protected function renderFooter(): void 222 | { 223 | if ($hidden = $this->getHiddenCount()) { 224 | printf("and %s more slow tests hidden from view\n", $hidden); 225 | } 226 | } 227 | 228 | /** 229 | * Populate options into class internals. 230 | */ 231 | protected function loadOptions(array $options): void 232 | { 233 | $this->slowThreshold = $options['slowThreshold'] ?? 500; 234 | $this->reportLength = $options['reportLength'] ?? 10; 235 | } 236 | 237 | /** 238 | * Calculate slow test threshold for given test. A TestCase may override the 239 | * suite-wide slowness threshold by using the annotation {@slowThreshold} 240 | * with a threshold value in milliseconds. 241 | * 242 | * For example, the following test would be considered slow if its execution 243 | * time meets or exceeds 5000ms (5 seconds): 244 | * 245 | * 246 | * \@slowThreshold 5000 247 | * public function testLongRunningProcess() {} 248 | * 249 | */ 250 | protected function getSlowThreshold(string $test): int 251 | { 252 | list($class, $testName) = explode('::', $test); 253 | $ann = TestUtil::parseTestMethodAnnotations($class, $testName); 254 | 255 | return isset($ann['method']['slowThreshold'][0]) ? (int) $ann['method']['slowThreshold'][0] : $this->slowThreshold; 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # phpunit-speedtrap 2 | 3 | [![Integrate](https://github.com/johnkary/phpunit-speedtrap/workflows/Integrate/badge.svg?branch=master)](https://github.com/johnkary/phpunit-speedtrap/actions) 4 | 5 | SpeedTrap reports on slow-running PHPUnit tests right in the console. 6 | 7 | Many factors affect test execution time. A test not properly isolated from variable latency (database, network, etc.) and even basic load on the test machine will cause test execution times to fluctuate. 8 | 9 | SpeedTrap helps **identify slow tests** but cannot explain **why** those tests are slow. Consider using [Blackfire.io](https://blackfire.io) to profile the test suite to specifically identify slow code. 10 | 11 | ![Screenshot of terminal using SpeedTrap](https://user-images.githubusercontent.com/135607/196077193-ba9e5f95-91ef-4655-88a5-93bb49007a67.png) 12 | 13 | ## Installation 14 | 15 | SpeedTrap is installed using [Composer](http://getcomposer.org). Add it as a `require-dev` dependency: 16 | 17 | composer require --dev johnkary/phpunit-speedtrap 18 | 19 | 20 | ## Usage 21 | 22 | Enable with all defaults by adding the following code to your project's `phpunit.xml` file: 23 | 24 | ```xml 25 | 26 | ... 27 | 28 | 29 | 30 | 31 | ``` 32 | 33 | Now run the test suite. If one or more test executions exceed the slowness threshold (500ms by default), SpeedTrap will report on those tests in the console after all tests have completed. 34 | 35 | ## Config Parameters 36 | 37 | SpeedTrap also supports these parameters: 38 | 39 | * **slowThreshold** - Number of milliseconds when a test is considered "slow" (Default: 500ms) 40 | * **reportLength** - Number of slow tests included in the report (Default: 10 tests) 41 | 42 | Each parameter is set in `phpunit.xml`: 43 | 44 | ```xml 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 500 54 | 55 | 56 | 10 57 | 58 | 59 | 60 | 61 | 62 | 63 | ``` 64 | 65 | ## Custom slowness threshold per-test case 66 | 67 | Some projects have a few complex tests that take a long time to run. It is possible to set a different slowness threshold for individual test cases. 68 | 69 | The annotation `@slowThreshold` can set a custom slowness threshold for each test case. This number may be higher or lower than the default threshold and is used instead of the default threshold for that specific test. 70 | 71 | ```php 72 | class SomeTestCase extends PHPUnit\Framework\TestCase 73 | { 74 | /** 75 | * @slowThreshold 5000 76 | */ 77 | public function testLongRunningProcess() 78 | { 79 | // Code that takes a longer time to execute 80 | } 81 | } 82 | ``` 83 | 84 | Setting `@slowThreshold 0` will never report that test as slow. 85 | 86 | ## Disable slowness profiling using an environment variable 87 | 88 | SpeedTrap profiles for slow tests when enabled in phpunit.xml. But using an environment variable named `PHPUNIT_SPEEDTRAP` can enable or disable the extension: 89 | 90 | $ PHPUNIT_SPEEDTRAP="disabled" ./vendor/bin/phpunit 91 | 92 | #### Use case: Disable profiling in development, but profile with Travis CI 93 | 94 | Travis CI is popular for running tests in the cloud after pushing new code to a repository. 95 | 96 | Step 1) Enable SpeedTrap in phpunit.xml, but set `PHPUNIT_SPEEDTRAP="disabled"` to disable profiling when running tests. 97 | 98 | ```xml 99 | 100 | ... 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | ``` 110 | 111 | Step 2) Configure `.travis.yml` with `PHPUNIT_SPEEDTRAP="enabled"` to profile for slow tests when running on Travis CI: 112 | 113 | ```yaml 114 | language: php 115 | 116 | php: 117 | - 7.3 118 | 119 | env: 120 | - PHPUNIT_SPEEDTRAP="enabled" 121 | ``` 122 | 123 | Step 3) View the Travis CI build output and read the slowness report printed in the console. 124 | 125 | [Travis CI Documentation - Environment Variables](https://docs.travis-ci.com/user/environment-variables) 126 | 127 | #### Use case: Enable profiling in development, but disable with Travis CI 128 | 129 | Step 1) Enable SpeedTrap in phpunit.xml. The slowness report will output during all test suite executions. 130 | 131 | ```xml 132 | 133 | ... 134 | 135 | 136 | 137 | 138 | ``` 139 | 140 | Step 2) Configure `.travis.yml` with `PHPUNIT_SPEEDTRAP="disabled"` to turn off profiling when running on Travis CI: 141 | 142 | ```yaml 143 | language: php 144 | 145 | php: 146 | - 7.3 147 | 148 | env: 149 | - PHPUNIT_SPEEDTRAP="disabled" 150 | ``` 151 | 152 | Step 3) View the Travis CI build output and confirm the slowness report is not printed in the console. 153 | 154 | #### Use case: Only enable SpeedTrap on demand via command-line 155 | 156 | Useful when you only want to profile slow tests once in a while. 157 | 158 | Step 1) Setup phpunit.xml to enable SpeedTrap, but disable slowness profiling by setting `PHPUNIT_SPEEDTRAP="disabled"` like this: 159 | 160 | ```xml 161 | 162 | ... 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | ``` 172 | 173 | Step 2) When executing `phpunit` from the command-line, enable slowness profiling only for this run by passing the environment variable `PHPUNIT_SPEEDTRAP="enabled"` like this: 174 | 175 | ```bash 176 | $ PHPUNIT_SPEEDTRAP=enabled ./vendor/bin/phpunit 177 | ``` 178 | 179 | ## Using with Symfony Framework 180 | 181 | [Symfony Framework](https://symfony.com/) comes with package [symfony/phpunit-bridge](https://packagist.org/packages/symfony/phpunit-bridge) that installs its own version of PHPUnit and **ignores** what is defined in your project's composer.json or composer.lock file. See the PHPUnit versions it installs with command `ls vendor/bin/.phpunit/` 182 | 183 | symfony/phpunit-bridge allows environment variable `SYMFONY_PHPUNIT_REQUIRE` to define additional dependencies while installing phpunit. 184 | 185 | The easiest way to set environment variables for the script `simple-phpunit` is via phpunit.xml.dist: 186 | 187 | ```xml 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | ``` 199 | 200 | Using the above example, running `vendor/bin/simple-phpunit` will now install the latest PHPUnit 9 and require the latest phpunit-speedtrap v4. 201 | 202 | ## Development 203 | 204 | Follow these steps to add new features or develop your own fork: 205 | 206 | ``` 207 | # Get source code (or replace with your fork URL) 208 | $ git checkout https://github.com/johnkary/phpunit-speedtrap.git phpunit-speedtrap 209 | 210 | # Install dev dependencies 211 | $ cd phpunit-speedtrap 212 | $ composer install 213 | 214 | # Run test suite to verify code runs as expected 215 | $ vendor/bin/phpunit 216 | ``` 217 | 218 | ## Inspiration 219 | 220 | SpeedTrap was inspired by [RSpec's](https://github.com/rspec/rspec) `--profile` option that displays feedback about slow tests. 221 | 222 | ## License 223 | 224 | phpunit-speedtrap is available under the MIT License. 225 | --------------------------------------------------------------------------------