Test Class Without Test

ID

php.test_class_without_test

Severity

critical

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Code Smell

Language

Php

Tags

coverage, reliability, testing

Description

Reports a class that presents itself as a PHPUnit fixture — it extends a TestCase base, or its name follows the …Test convention — yet declares no test method. The class runs no test, so whatever it was written to cover is not covered, and the gap is invisible because the file reads as part of a passing suite.

Rationale

PHPUnit collects a fixture and then finds nothing in it to run. The suite still goes green, the coverage report still counts the class, and nobody is told that the checks the class was created for are missing. The usual causes are a migration that dropped the markers a test method needs, a class left behind after its tests moved elsewhere, and setup scaffolding that never got its test.

All three markers PHPUnit accepts count as a test, so a fixture written in any of the three styles is left alone:

<?php

class OrderShipmentTest extends TestCase // FLAW — a fixture that covers nothing
{
    protected function setUp(): void
    {
        $this->subject = new OrderShipment();
    }

    private function anOrder(): Order
    {
        return new Order(1, 'pending');
    }
}

class ShippingRatesTest extends TestCase // OK — named by the test convention
{
    public function testRatesAreQuotedPerZone(): void
    {
        $this->assertSame(3, count($this->subject->rates()));
    }
}

class PackagingTest extends TestCase // OK — marked by the attribute
{
    #[Test]
    public function ratesAreQuotedPerZone(): void
    {
        $this->assertTrue($this->subject->isPacked());
    }
}

The following are not reported, because each is correct as it stands:

  • an abstract class, a trait and an interface — PHPUnit collects none of them on its own;

  • a class whose name ends in TestCase, the convention for a shared base;

  • a class that another class in the same file extends — the base holds the setup, the subclasses hold the tests;

  • a class deriving from a base this analysis cannot see, since an unseen base may hold the tests;

  • a class inheriting a test from a base declared in the same file;

  • a class whose only fixture signal is its name while it exposes a public property — the shape of a domain type that merely happens to be called BloodTest.

Remediation

Write the test the class was created for, or delete the class if the behaviour it covered is already tested elsewhere. When the class exists only to be extended, make it abstract or name it …TestCase so both PHPUnit and the reader can see that it is a base and not a fixture.

<?php

class OrderShipmentTest extends TestCase
{
    protected function setUp(): void
    {
        $this->subject = new OrderShipment();
    }

    public function testShippingAnOrderMarksItDispatched(): void
    {
        $this->subject->ship($this->anOrder());

        $this->assertTrue($this->subject->isDispatched());
    }

    private function anOrder(): Order
    {
        return new Order(1, 'pending');
    }
}

abstract class AbstractShippingTestCase extends TestCase
{
    protected function subject(): OrderShipment
    {
        return new OrderShipment();
    }
}

© 2026 Xygeni. All rights reserved