Test Class Without Test
ID |
java.test_class_without_test |
Severity |
critical |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Code Smell |
Language |
Java |
Tags |
coverage, reliability, testing |
Description
Reports a class that presents itself as a test fixture — a JUnit 3 TestCase, a JUnit 4 @RunWith fixture, a Spring @SpringBootTest, or a class named …Test/…Tests/…TestCase — yet declares no test method.
Tests inherited from an abstract base class count as declared, wherever that base class lives: the one-line subclass of a shared base that holds every @Test is the commonest test-suite shape there is. So do tests declared in a nested class at any depth, which is how JUnit 5 @Nested groups are organised. Cucumber step definitions (io.cucumber.java8.En) are not reported either — their steps are lambdas registered in the constructor and are never methods.
Rationale
A fixture with no test contributes nothing, but it looks exactly like a passing test. Whatever the class was written to cover stays uncovered, and nobody notices, because the class runs green in every build. The usual cause is a test attribute dropped during a framework migration, or a fixture whose tests were moved out and never deleted along with it.
class OrderPricingTest {
OrderPricingCalculator calculator = new OrderPricingCalculator();
void setUpFixture() {
calculator.reset();
}
}
Remediation
Add the test method the class was meant to hold, or delete the class if its coverage moved elsewhere.
class OrderPricingTest {
OrderPricingCalculator calculator = new OrderPricingCalculator();
@Test
void appliesBulkDiscount() {
calculator.reset();
assertEquals(90, calculator.priceFor(10, 100));
}
}
What is not reported
An abstract class is left alone — it is designed for a subclass to extend, not to run on its own. A class whose only fixture signal is its name is left alone too when it declares a public field, since that is the shape of a domain type that happens to be named …Test (a BloodTest), not of a fixture. A class that any other class in the analysed source extends is also left alone: a shared fixture base legitimately declares only setup, with the tests living in its subclasses — which normally sit in their own files.
An ArchUnit suite is not reported. ArchUnit states its checks as @ArchTest fields or methods rather than as @Test methods, so such a class declares no test method by construction; the @AnalyzeClasses marker its runner requires is enough on its own, since a suite may inherit or import every rule it runs.
The name has to read as <UnitUnderTest>Test. A class named nothing but the suffix — the TestCase parameterised-data holder — or one whose name ends in a purpose phrase such as …HelpersForTest is not matched by the naming signal, although a @RunWith/@SpringBootTest marker on it still is.
Zohhak, Cucumber and Pact fixtures are not recognised, nor is TestNG’s class-level @Test (which implies every public method is a test), nor a test method inherited from an interface declared in another file.