Test Class Name Suffix
ID |
php.test_class_names_end_test |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
Php |
Tags |
naming, reliability, testing |
Description
Reports a PHPUnit fixture that declares test methods but whose name does not end in Test. Those
tests never run.
Rationale
PHPUnit discovers a suite by file name suffix — *Test.php out of the box — and PSR-4 autoloading
requires the file to be named after the class it declares. A fixture called CatalogSearch or
PricingSpec therefore lives in a file the runner never opens: every case it holds is silently
skipped. That is worse than a failing test, because nothing points at the gap — the suite reports
success and the coverage report simply shows the covered code as uncovered.
<?php
class CatalogSearch extends TestCase // FLAW — CatalogSearch.php is never collected
{
public function testSearchMatchesByTitle(): void
{
$this->assertCount(2, (new CatalogSearch())->byTitle('php'));
}
}
class StockLevelsTests extends TestCase // FLAW — the runner collects "…Test", not "…Tests"
{
public function testStockIsReserved(): void
{
$this->assertTrue((new Stock())->reserve('sku-1'));
}
}
class CatalogSearchTest extends TestCase // OK
{
public function testSearchMatchesByTitle(): void
{
$this->assertCount(2, (new CatalogSearch())->byTitle('php'));
}
}
Only a class that both extends a TestCase base and declares at least one test method is reported.
The inheritance is what makes it a fixture rather than a domain type, and requiring a test method
keeps the finding actionable — it names cases that are actually being skipped — while leaving a
test-free fixture to php.test_class_without_test, which is the defect there.
Not reported: an abstract class, a trait and an interface, none of which the runner collects; a
class whose name ends in TestCase, the convention for a shared base; and a class another class in
the same file extends, which is that same shape spelled out.
Remediation
Rename the class — and its file with it — so the name carries the suffix the runner collects.
<?php
// CatalogSearchTest.php
class CatalogSearchTest extends TestCase
{
public function testSearchMatchesByTitle(): void
{
$this->assertCount(2, (new CatalogSearch())->byTitle('php'));
}
}
If the class is a shared base rather than a suite, make it abstract or name it …TestCase, so
both the runner and the reader can see that it holds no cases of its own.