├── .gitignore
├── README.md
├── composer.json
└── src
└── Ozh
└── PHPUnit
└── Listener
└── OverAssertiveTestsListener.php
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | /composer.lock
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # phpunit-overassertive
2 |
3 | Having several assertions in the same test is fine, but when an assertion fails,
4 | the whole test aborts and other assertions in the same test are not tested.
5 |
6 | Depending on what you test and how you coded it, you may want to split some tests
7 | in several sub tests.
8 |
9 | **OverAssertive** is a PHPUnit extension that reports right in the console
10 | which tests have "too many" assertions, where "too many" is what you define, to
11 | help you inspect and maybe refactor some tests.
12 |
13 | 
14 |
15 | ## Usage
16 |
17 | Enable it with all defaults by adding the following to your test suite's `phpunit.xml` file:
18 |
19 | ```xml
20 |
21 | ...
22 |
23 |
24 |
25 |
26 | ```
27 |
28 | If you're not using an autoloader you can also specify the library location:
29 |
30 | ```xml
31 |
32 | ...
33 |
34 |
35 |
36 |
37 | ```
38 |
39 | Now run your test suite as normal. OverAssertive will report over assertive tests in the console after the suite completes.
40 |
41 | ## Configuration
42 |
43 | OverAssertive has two configurable parameters:
44 |
45 | * **alertThreshold** - Number of assertions that will make a test over assertive (default: 10 assertions)
46 | * **reportLength** - Number of over assertive tests included in the report (default: 10 tests)
47 |
48 | These configuration parameters are set in `phpunit.xml` when adding the listener:
49 |
50 | ```xml
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | 10
60 |
61 |
62 | 10
63 |
64 |
65 |
66 |
67 |
68 |
69 | ```
70 |
71 | ## Inspiration
72 |
73 | Much thanks to [phpunit-speedtrap](https://github.com/johnkary/phpunit-speedtrap)
74 |
75 | ## License
76 |
77 | Do whatever the hell you want to.
78 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ozh/phpunit-overassertive",
3 | "description": "Find over assertive tests in your PHPUnit test suite",
4 | "keywords": ["PHPUnit", "assertion", "listener"],
5 | "homepage": "https://github.com/ozh/phpunit-overassertive",
6 | "type": "library",
7 | "license": "WTFPL",
8 | "authors": [
9 | {
10 | "name": "Ozh",
11 | "homepage": "http://ozh.org/"
12 | }
13 | ],
14 | "require": {
15 | "php": ">=7"
16 | },
17 | "require-dev": {
18 | "phpunit/phpunit": ">=7"
19 | },
20 | "autoload": {
21 | "psr-0": {
22 | "ozh": "src/"
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Ozh/PHPUnit/Listener/OverAssertiveTestsListener.php:
--------------------------------------------------------------------------------
1 | loadOptions($options);
52 | }
53 |
54 | /**
55 | * An error occurred.
56 | *
57 | * @param \PHPUnit\Framework\Test $test
58 | * @param Throwable $e
59 | * @param float $time
60 | */
61 | public function addError(\PHPUnit\Framework\Test $test, \Throwable $e, $time) :void
62 | {
63 | }
64 |
65 | /**
66 | * A failure occurred.
67 | *
68 | * @param \PHPUnit\Framework\Test $test
69 | * @param \PHPUnit_Framework_AssertionFailedError $e
70 | * @param float $time
71 | */
72 | public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, $time) :void
73 | {
74 | }
75 |
76 | /**
77 | * Incomplete test.
78 | *
79 | * @param \PHPUnit\Framework\Test $test
80 | * @param Throwable $e
81 | * @param float $time
82 | */
83 | public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Throwable $e, $time) :void
84 | {
85 | }
86 |
87 | /**
88 | * Risky test.
89 | *
90 | * @param \PHPUnit\Framework\Test $test
91 | * @param Throwable $e
92 | * @param float $time
93 | * @since Method available since Release 4.0.0
94 | */
95 | public function addRiskyTest(\PHPUnit\Framework\Test $test, \Throwable $e, $time) :void
96 | {
97 | }
98 |
99 | /**
100 | * Skipped test.
101 | *
102 | * @param \PHPUnit\Framework\Test $test
103 | * @param Throwable $e
104 | * @param float $time
105 | */
106 | public function addSkippedTest(\PHPUnit\Framework\Test $test, \Throwable $e, $time) :void
107 | {
108 | }
109 |
110 | /**
111 | * Warning.
112 | *
113 | * @param \PHPUnit\Framework\Test $test
114 | * @param Throwable $e
115 | * @param float $time
116 | */
117 | public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, $time) :void
118 | {
119 | }
120 |
121 | /**
122 | * A test started.
123 | *
124 | * @param \PHPUnit\Framework\Test $test
125 | */
126 | public function startTest(\PHPUnit\Framework\Test $test) :void
127 | {
128 | }
129 |
130 | /**
131 | * A test ended.
132 | *
133 | * @param \PHPUnit\Framework\Test $test
134 | */
135 | public function endTest(\PHPUnit\Framework\Test $test, $time) :void
136 | {
137 | if (!$test instanceof \PHPUnit\Framework\TestCase) return;
138 |
139 | $threshold = $this->alertThreshold;
140 | $assertions = \PHPUnit\Framework\Assert::getCount();
141 |
142 | if ($assertions > $this->alertThreshold) {
143 | $this->addAssertiveTest($test, $assertions);
144 | }
145 |
146 | }
147 |
148 | /**
149 | * A test suite started.
150 | *
151 | * @param \PHPUnit\Framework\TestSuite $suite
152 | */
153 | public function startTestSuite(\PHPUnit\Framework\TestSuite $suite) :void
154 | {
155 | $this->suites++;
156 | }
157 |
158 | /**
159 | * A test suite ended.
160 | *
161 | * @param \PHPUnit\Framework\TestSuite $suite
162 | */
163 | public function endTestSuite(\PHPUnit\Framework\TestSuite $suite) :void
164 | {
165 | $this->suites--;
166 |
167 | if (0 === $this->suites && $this->hasAssertiveTest()) {
168 | arsort($this->assertive); // Sort most assertive tests to the top
169 |
170 | $this->renderHeader();
171 | $this->renderBody();
172 | $this->renderFooter();
173 | }
174 | }
175 |
176 | /**
177 | * Stores a test as over assertive.
178 | *
179 | * @param \PHPUnit\Framework\TestCase $test
180 | * @param int $assertions Number of assertions
181 | */
182 | protected function addAssertiveTest(\PHPUnit\Framework\TestCase $test, $assertions) :void
183 | {
184 | $label = $this->makeLabel($test);
185 | $this->assertive[$label] = $assertions;
186 | }
187 |
188 | /**
189 | * Whether at least one test has been considered over assertive.
190 | *
191 | * @return bool
192 | */
193 | protected function hasAssertiveTest()
194 | {
195 | return !empty($this->assertive);
196 | }
197 |
198 | /**
199 | * Label for describing a test.
200 | *
201 | * @param \PHPUnit\Framework\TestCase $test
202 | * @return string
203 | */
204 | protected function makeLabel(\PHPUnit\Framework\TestCase $test)
205 | {
206 | return sprintf('%s:%s', get_class($test), $test->getName());
207 | }
208 |
209 | /**
210 | * Calculate number of over assertive tests to report about.
211 | *
212 | * @return int
213 | */
214 | protected function getReportLength()
215 | {
216 | return min(count($this->assertive), $this->reportLength);
217 | }
218 |
219 | /**
220 | * Find how many over assertive tests occurred that won't be shown due to list length.
221 | *
222 | * @return int Number of hidden over assertive tests
223 | */
224 | protected function getHiddenCount()
225 | {
226 | $total = count($this->assertive);
227 | $showing = $this->getReportLength($this->assertive);
228 |
229 | $hidden = 0;
230 | if ($total > $showing) {
231 | $hidden = $total - $showing;
232 | }
233 |
234 | return $hidden;
235 | }
236 |
237 | /**
238 | * Pluralize "assertion" if needed
239 | */
240 | protected function pluralizeAssertion($count)
241 | {
242 | return $count <= 1 ? 'assertion' : 'assertions';
243 | }
244 |
245 |
246 | /**
247 | * Renders test report header.
248 | */
249 | protected function renderHeader()
250 | {
251 | echo sprintf("\n\n%s more than %s %s:\n", count($this->assertive) == 1 ? 'This test has' : 'These tests have', $this->alertThreshold, $this->pluralizeAssertion($this->alertThreshold));
252 | }
253 |
254 | /**
255 | * Renders test report body.
256 | */
257 | protected function renderBody()
258 | {
259 | $assertive = $this->assertive;
260 | $max = strlen(max($assertive));
261 |
262 | $length = $this->getReportLength($assertive);
263 | for ($i = 1; $i <= $length; ++$i) {
264 | $label = key($assertive);
265 | $assertions = array_shift($assertive);
266 | $display = str_pad($assertions, $max, " ", STR_PAD_LEFT);
267 | $line = str_pad($i, strlen($length), " ", STR_PAD_LEFT);
268 |
269 | echo sprintf(" %s. %s %s in test %s\n", $line, $display, $this->pluralizeAssertion($assertions), $label);
270 | }
271 | }
272 |
273 | /**
274 | * Renders test report footer.
275 | */
276 | protected function renderFooter()
277 | {
278 | if ($hidden = $this->getHiddenCount($this->assertive)) {
279 | echo sprintf("...and there %s %s more above your threshold hidden from view", $hidden == 1 ? 'is' : 'are', $hidden);
280 | }
281 | }
282 |
283 | /**
284 | * Populate options into class internals.
285 | *
286 | * @param array $options
287 | */
288 | protected function loadOptions(array $options)
289 | {
290 | $this->alertThreshold = isset($options['alertThreshold']) ? $options['alertThreshold'] : 10;
291 | $this->reportLength = isset($options['reportLength']) ? $options['reportLength'] : 10;
292 | }
293 |
294 | }
295 |
--------------------------------------------------------------------------------