Skip to content

Commit 181c048

Browse files
committed
Initial commit
0 parents  commit 181c048

File tree

6 files changed

+366
-0
lines changed

6 files changed

+366
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/vendor
2+
/composer.lock

LICENSE

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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.

README.md

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# phpunit-speedtrap
2+
3+
Reports on slow-running tests in your PHPUnit test suite.
4+
5+
Enable by adding the following to your phpunit.xml:
6+
7+
```xml
8+
<phpunit>
9+
...
10+
<listeners>
11+
<listener class="JohnKary\PHPUnit\Listener\SpeedTrapListener">
12+
<array>
13+
<integer>500</integer> <!-- Slowness threshold in ms -->
14+
<integer>10</integer> <!-- Number of slow tests to report on -->
15+
</array>
16+
</listener>
17+
</listeners>
18+
</phpunit>
19+
```

composer.json

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "johnkary/phpunit-speedtrap",
3+
"description": "Find slow tests in your PHPUnit test suite",
4+
"keywords": ["PHPUnit", "slow", "profile"],
5+
"homepage": "",
6+
"type": "library",
7+
"license": "MIT",
8+
"authors": [
9+
{
10+
"name": "John Kary",
11+
"email": "[email protected]"
12+
}
13+
],
14+
"minimum-stability": "stable",
15+
"require": {
16+
"php": ">=5.3.0"
17+
},
18+
"require-dev": {
19+
"phpunit/phpunit": "3.7.*"
20+
},
21+
"autoload": {
22+
"psr-0": {
23+
"JohnKary": "src/"
24+
}
25+
}
26+
}

phpunit.xml.example

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<!-- http://www.phpunit.de/manual/current/en/appendixes.configuration.html -->
4+
<phpunit
5+
backupGlobals = "false"
6+
backupStaticAttributes = "false"
7+
colors = "true"
8+
convertErrorsToExceptions = "true"
9+
convertNoticesToExceptions = "true"
10+
convertWarningsToExceptions = "true"
11+
processIsolation = "false"
12+
stopOnFailure = "false"
13+
syntaxCheck = "false"
14+
bootstrap = "vendor/autoload.php">
15+
16+
<testsuites>
17+
<testsuite name="Project Test Suite">
18+
<directory>src/*/*/*/Tests</directory>
19+
</testsuite>
20+
</testsuites>
21+
22+
<listeners>
23+
<listener class="JohnKary\PHPUnit\Listener\SpeedTrapListener">
24+
<arguments>
25+
<integer>455</integer>
26+
<integer>10</integer>
27+
</arguments>
28+
</listener>
29+
</listeners>
30+
31+
<filter>
32+
<whitelist>
33+
<directory>src</directory>
34+
<exclude>
35+
<directory>src/*/Tests</directory>
36+
</exclude>
37+
</whitelist>
38+
</filter>
39+
40+
</phpunit>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
<?php
2+
3+
namespace JohnKary\PHPUnit\Listener;
4+
5+
/**
6+
* A PHPUnit TestListener that exposes your slowest running tests by outputting
7+
* results directly to the console.
8+
*/
9+
class SpeedTrapListener implements \PHPUnit_Framework_TestListener
10+
{
11+
/**
12+
* Internal tracking for test suites.
13+
*
14+
* Increments as more suites are run, then decremented as they finish. All
15+
* suites have been run when returns to 0.
16+
*
17+
* @var integer
18+
*/
19+
protected $suites = 0;
20+
21+
/**
22+
* Time in milliseconds at which a test will be considered "slow" and
23+
* be reported by this listener.
24+
*
25+
* @var int
26+
*/
27+
private $slowThreshold;
28+
29+
/**
30+
* Number of tests to report on for slowness.
31+
*
32+
* @var int
33+
*/
34+
protected $reportLength;
35+
36+
/**
37+
* Collection of slow tests
38+
*
39+
* @var array
40+
*/
41+
protected $slow = array();
42+
43+
public function __construct($slowThreshold = 500, $reportLength = 10)
44+
{
45+
$this->slowThreshold = $slowThreshold;
46+
$this->reportLength = $reportLength;
47+
}
48+
49+
/**
50+
* An error occurred.
51+
*
52+
* @param \PHPUnit_Framework_Test $test
53+
* @param \Exception $e
54+
* @param float $time
55+
*/
56+
public function addError(\PHPUnit_Framework_Test $test, \Exception $e, $time)
57+
{
58+
}
59+
60+
/**
61+
* A failure occurred.
62+
*
63+
* @param \PHPUnit_Framework_Test $test
64+
* @param \PHPUnit_Framework_AssertionFailedError $e
65+
* @param float $time
66+
*/
67+
public function addFailure(\PHPUnit_Framework_Test $test, \PHPUnit_Framework_AssertionFailedError $e, $time)
68+
{
69+
}
70+
71+
/**
72+
* Incomplete test.
73+
*
74+
* @param \PHPUnit_Framework_Test $test
75+
* @param \Exception $e
76+
* @param float $time
77+
*/
78+
public function addIncompleteTest(\PHPUnit_Framework_Test $test, \Exception $e, $time)
79+
{
80+
}
81+
82+
/**
83+
* Skipped test.
84+
*
85+
* @param \PHPUnit_Framework_Test $test
86+
* @param \Exception $e
87+
* @param float $time
88+
*/
89+
public function addSkippedTest(\PHPUnit_Framework_Test $test, \Exception $e, $time)
90+
{
91+
}
92+
93+
/**
94+
* A test started.
95+
*
96+
* @param \PHPUnit_Framework_Test $test
97+
*/
98+
public function startTest(\PHPUnit_Framework_Test $test)
99+
{
100+
}
101+
102+
/**
103+
* A test ended.
104+
*
105+
* @param \PHPUnit_Framework_Test $test
106+
* @param float $time
107+
*/
108+
public function endTest(\PHPUnit_Framework_Test $test, $time)
109+
{
110+
$time = $this->toMilliseconds($time);
111+
if ($this->isSlow($time)) {
112+
$this->addSlowTest($test, $time);
113+
}
114+
}
115+
116+
/**
117+
* A test suite started.
118+
*
119+
* @param \PHPUnit_Framework_TestSuite $suite
120+
*/
121+
public function startTestSuite(\PHPUnit_Framework_TestSuite $suite)
122+
{
123+
$this->suites++;
124+
}
125+
126+
/**
127+
* A test suite ended.
128+
*
129+
* @param \PHPUnit_Framework_TestSuite $suite
130+
*/
131+
public function endTestSuite(\PHPUnit_Framework_TestSuite $suite)
132+
{
133+
$this->suites--;
134+
135+
if (0 === $this->suites && $this->hasSlowTests()) {
136+
arsort($this->slow); // Sort longest running tests to the top
137+
138+
$this->renderHeader();
139+
$this->renderBody();
140+
$this->renderFooter();
141+
}
142+
}
143+
144+
/**
145+
* Whether the given test execution time is considered slow.
146+
*
147+
* @param int $time Test execution time in milliseconds
148+
* @return bool
149+
*/
150+
protected function isSlow($time)
151+
{
152+
return $time >= $this->slowThreshold;
153+
}
154+
155+
/**
156+
* Stores a test as slow.
157+
*
158+
* @param \PHPUnit_Framework_TestCase $test
159+
* @param int $time Test execution time in milliseconds
160+
*/
161+
protected function addSlowTest(\PHPUnit_Framework_TestCase $test, $time)
162+
{
163+
$label = $this->makeLabel($test);
164+
165+
$this->slow[$label] = $time;
166+
}
167+
168+
/**
169+
* Whether at least one test has been considered slow.
170+
*
171+
* @return bool
172+
*/
173+
protected function hasSlowTests()
174+
{
175+
return !empty($this->slow);
176+
}
177+
178+
/**
179+
* Convert PHPUnit's reported test time (microseconds) to milliseconds.
180+
*
181+
* @param float $time
182+
* @return int
183+
*/
184+
private function toMilliseconds($time)
185+
{
186+
return (int) round($time * 1000);
187+
}
188+
189+
/**
190+
* Label for describing a test
191+
*
192+
* @param \PHPUnit_Framework_TestCase $test
193+
* @return string
194+
*/
195+
private function makeLabel(\PHPUnit_Framework_TestCase $test)
196+
{
197+
return sprintf('%s:%s', get_class($test), $test->getName());
198+
}
199+
200+
/**
201+
* Calculate number of slow tests to report about.
202+
*
203+
* @return int
204+
*/
205+
private function getReportLength()
206+
{
207+
return min(count($this->slow), $this->reportLength);
208+
}
209+
210+
/**
211+
* Find how many slow tests occurred that won't be shown due to list length.
212+
*
213+
* @return int Number of hidden slow tests
214+
*/
215+
private function getHiddenCount()
216+
{
217+
$total = count($this->slow);
218+
$showing = $this->getReportLength($this->slow);
219+
220+
$hidden = 0;
221+
if ($total > $showing) {
222+
$hidden = $total - $showing;
223+
}
224+
225+
return $hidden;
226+
}
227+
228+
/**
229+
* Renders slow test report header
230+
*/
231+
protected function renderHeader()
232+
{
233+
echo sprintf("\n\nYou should really fix these slow tests (>%sms)...\n", $this->slowThreshold);
234+
}
235+
236+
/**
237+
* Renders slow test report body
238+
*/
239+
protected function renderBody()
240+
{
241+
$length = $this->getReportLength($this->slow);
242+
for ($i = 1; $i <= $length; ++$i) {
243+
$label = key($this->slow);
244+
$time = array_shift($this->slow);
245+
246+
echo sprintf(" %s. %sms to run %s\n", $i, $time, $label);
247+
}
248+
}
249+
250+
/**
251+
* Renders slow test report footer
252+
*/
253+
protected function renderFooter()
254+
{
255+
if ($hidden = $this->getHiddenCount($this->slow)) {
256+
echo sprintf("...and there are %s more above your threshold hidden from view", $hidden);
257+
}
258+
}
259+
}

0 commit comments

Comments
 (0)