├── .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 | [](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 | 
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 |
--------------------------------------------------------------------------------