sebastianbergmann / phpunit

The PHP Unit Testing framework.
https://phpunit.de/
BSD 3-Clause "New" or "Revised" License
19.68k stars 2.2k forks source link

Allow `Uses*` attributes to prevent code coverage #5968

Open JonathanGawrych opened 2 weeks ago

JonathanGawrych commented 2 weeks ago

Right now, the @uses annotation, or the UsesClass or UsesFunction attributes are only useful in the context of Unintentionally Covered Code when running in strict coverage mode.

Our project at work has many legacy layers, and not all those layers are well thought out. It would be very difficult for us to annotate with each test all the parts that a tests covers (we have a controller test, that calls an action, that calls a service, that calls a model, that interacts with events, which call a listener, which calls... etc). In a perfect world we'd test each part in isolation, but with legacy code, it tends to be a lot of work to set up a single layer to test, and it prevents refactoring without a lot of test rework.

This gives us the desire to opt-out parts of the code coverage, rather than opting in. We want the tests to cover the controller, services, and models, but not our middleware, service providers, or other boot up code. We want explicit tests to cover those instead.

Would it be possible to make the @uses/UsesClass/UsesFunction function without @covers/CoversClass/CoversFunction, and disables coverage for those classes/functions?

For example:

class Controller {
    function coverThis() {
        return (new Service())->andCoverThis();
    }
}

class Service {
    function andCoverThis() {
        (new Logger())->butDontCoverThis('service called!');
        return 123;
    }
}

class Logger {
    function butDontCoverThis($message) {
        file_put_contents('logs.txt', $message . PHP_EOL, FILE_APPEND);
    }
}

#[UsesClass(Logger)]
class ControllerTest extends TestCase {
    function testControllerCoverThis() {
        static::assertSame(123, (new Controller())->coverThis());
    }
}

In this case, I want the Controller and Service to be covered, but not my logger. That way I'll see the lack of coverage on Logger, and write specific tests for it. This example may seem trivial (just add Covers instead), but imagine 4 more layers beyond service, each layer calling multiple different things. Excluding the one class is a lot easier and less brittle than including dozens of classes.

sebastianbergmann commented 1 day ago

Right now, the @uses annotation, or the UsesClass or UsesFunction attributes are only useful in the context of Unintentionally Covered Code when running in strict coverage mode.

Not true anymore: the --uses CLI option can be used to filter tests based on this information.