Search code examples
phpunit-testingphpunitcode-coveragephp-code-coverage

How to exclude a method from code coverage processing in PHP unit if you set processUncoveredFilesFromWhitelist="true"


I've set in my phpunit.xml to do the following:

<filter>
    <whitelist  addUncoveredFilesFromWhitelist="true" processUncoveredFilesFromWhitelist="true">
       <directory suffix=".php">/my/app</directory>
    </whielist>
</filter> 

I run the tests with:

 phpunit -c /path/to/phpunit.xml --coverage-text

However, there is one method in one file I wish never to be evaluated for coverage as it outputs a bunch of garbage to my text test coverage report. Let's assume the class/method is named my\app\Response::render()

A simplified version of this method is:

public function render($response) {
     header($response['status']);
     echo $response['body'];
     echo "\n";
}

This render() method issues a header() command as well as a number of echo() commands which cause much text and errors to be thrown when running coverage reports with --coverage-text.

I've attempted the following:

  • Added the annotation @codeCoverageIgnore
  • To my whitelist block in the xml, tried to exclude the whole file: <exclude><file>/my/app/Response.php</file></exclude>

Neither prevent the method from being executed (in some way).

I've considered a hack like adding some code into this method checking to see if we're being executed via phpunit like wrapping the code in:

 if(strpos($GLOBALS['argv'][0], 'phpunit') !==false){
     // ... do stuff
 }

Or, like using a good old $do_debug variable method, passing an environment variable in via phpunit, specified in the phpunit.xml to disable this code (this way if later I want to run phpunit and have the method execute I can by just toggling the environment variable).

None of these options seem elegant. I don't like modifying my code to work around testing issues (I even get a little unhappy adding test hint annotations to my methods, I rather it be handled with a white/black list of some sort).

However, when some perceived limitation requiring a less-than-elegant solution pops up in a well vetted set of libraries, I like to check my head against the community and see if something's been overlooked.

Anyone know how I can achieve prevention of this method from being processed just within the functionality of Phpunit?


Solution

  • Apparently, methods not invoked explicitly by tests don't get executed--with or without processUncoveredFilesFromWhitelist="true". Only global & static functions called from a test; methods of an instantiated class called in a test; or anything similar to the prior two items down the stack of dependencies which cascade from a test's actions get run (as far as I can tell).

    Unless you use some form of mocking, test-double, etc... to replace or skirt around the method in question, nothing exist to prevent execution of a method being called when running your unit tests and checking coverage.

    This makes total sense.

    It turns out in my case one of the files found somewhere in the in the /my/app directory was a vanilla PHP file (not a class file).

    When you specify processUncoveredFilesFromWhitelist="true", if that file has any globally executable php, it will get run. Any classes it instantiates or methods/functions it calls, are just like any other code execution.

    I'm using a custom MVC web framework that follows a common PHP paradigm. Inside

      /my/app/public
    

    there's a plain index.php that would instantiate a new app and execute it. Something like:

    <?php
    
    $app = new app\MyApp();
    $app->handleRequest();
    

    By turning on processUncoveredFilesFromWhitelist="true" index.php reacted the same way it would as if it got triggered by a Fast CGI request from nginx to php-fpm configured to use the file--it started the app.

    I added the file /my/app/public/index.php to my whitelist block:

    <filter>
        <whitelist  addUncoveredFilesFromWhitelist="true" processUncoveredFilesFromWhitelist="true">
            <directory suffix=".php">/my/app</directory>
            <exclude>
                <file>/my/app/public/index.php</file>
            </exclude>
        </whitelist>
    </filter>