4

I have a test environment that runs the component tests for a product. I found that recently it was tough to test and successfully mock php's is_uploaded_file() and move_uploaded_file() but after a lot of searching and research I came upon PHPT. This really helped me a lot with testing those methods and expectations for file uploading. This is not a question about file uploading but how to integrate the phpt testcases into the basic phpunit testcases so that code coverage will run for the methods that are being tested as well. Here follows some code extracts:

files.php

class prFiles
{
    // Instance methods here not needed for the purpose of this question
    // ......

    public function transfer(array $files, $target_directory,
        $new_filename, $old_filename = '')
    {
        if ( (isset($files['file']['tmp_name']) === true)
            && (is_uploaded_file($files['file']['tmp_name']) === true) )
        {
            // Only check if old filename exists
            if ( (file_exists($target_directory . '/' . $old_filename) === true)
                && (empty($old_filename) === false) )
            {
                unlink($target_directory . $old_filename);
            }
            $upload = move_uploaded_file(
                $files['file']['tmp_name'],
                $target_directory . '/' . $new_filename
            );

            if ( $upload === true )
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        return false;

    }
}

file_upload_test.phpt

--TEST--
Test the prFiles::transfer() the actual testing of the file uploading.
--POST_RAW--
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryfywL8UCjFtqUBTQn

------WebKitFormBoundaryfywL8UCjFtqUBTQn
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain

This is some test text

------WebKitFormBoundaryfywL8UCjFtqUBTQn
Content-Disposition: form-data; name="submit"

Upload
------WebKitFormBoundaryfywL8UCjFtqUBTQn--
--FILE--
<?php
require_once dirname(__FILE__) . '/../../../src/lib/utilities/files.php';

$prFiles = prFiles::getInstance()->transfer(
    $_FILES,
    dirname(__FILE__) . '/../_data/',
    'test.txt'
);

var_dump($prFiles);

?>
--EXPECT--
bool(true)

UtilitiesFilesTransferTest.php

class UtilitiesFilesTransferTest extends PHPUnit_Extensions_PhptTestCase
{

    /**
     * Constructs a new UtilitiesFilesTransferTest.
     *
     */
    public function __construct()
    {
        parent::__construct(dirname(__FILE__) . '/_phpt/file_upload_test.phpt');

    }

}

So that all works. But I can't seem to get any coverage of the transfer method I am testing. Please can anyone help me?

EDIT: my coverage command looks like this :

@echo off
echo.
if not "%1"=="" goto location
goto default

:location
set EXEC=phpunit --coverage-html %1 TestSuite
goto execute

:default
set EXEC=phpunit --coverage-html c:\xampp\htdocs\workspace\coverage\project TestSuite

:execute
%EXEC%
Etienne Marais
  • 1,660
  • 1
  • 22
  • 40
  • What is your environment? (PHP/XDebug/PHPUnit version and operating system) – Christopher Manning Apr 17 '11 at 01:55
  • Do you have xdebug enabled in your cgi `php.ini` file? Testing uploads with phpt uses CGI, not the normal CLI. – cweiske Apr 17 '11 at 19:51
  • @cweiske Is there some documentation on that? its the first time i've heard of it and is eager to learn. @CM It's not to do with what my environment is but thx anyway. – Etienne Marais Apr 18 '11 at 07:12
  • "first time i've heard of it" - what? xdebug? php.ini? cgi? – cweiske Apr 18 '11 at 07:31
  • LOL that phpt uses cgi. I am new to phpt testing, not to php and unit tests itself. – Etienne Marais Apr 18 '11 at 07:45
  • Sorry, can't make sense of the coverage command - I assume you've got coverage working on other classes. However, I think you could refactor your class under test to make it easier to test - the whole nested if statement thing looks like a great place for bugs to hide, and subsequent maintenance coders could struggle to make sense of it all. For instance, I might consider the if statement after the "Only check if old filename exists" statement a candidate for "extract method" refactoring. I think you might make some progress by isolating exactly which bit of your code refuses coverage... – Neville Kuyt Apr 21 '11 at 23:04
  • You're not on Mac are you? https://github.com/sebastianbergmann/php-code-coverage/issues/25 :-) – Potherca Sep 07 '12 at 21:53

2 Answers2

1

As PhpUnit has custom implementation for running PHPT files which occurs in a separate process, getting code coverage integrated with PhpUnit could prove to be quite difficult indeed.

However, if all you need is coverage (or you are willing to do some post-processing yourself), it becomes quite trivial.

In its most simple form, all that you need to do is make calls to xDebug from your PHPT files. Using PHP_CodeCoverage (and Composer for class autoloading) your --FILE-- section could look like this:

--FILE--
<?php
/* autoload classes */
require __DIR__ . '/../../../vendor/autoload.php';

/* Setup and start code coverage */
$coverage = new \PHP_CodeCoverage;
$coverage->start('test');

/* run logic */
$prFiles = prFiles::getInstance()->transfer(
    $_FILES,
    __DIR__ . '/../_data/',
    'test.txt'
);
var_dump($prFiles);


/* stop and output coverage data */
$coverage->stop();
$writer = new \PHP_CodeCoverage_Report_PHP;
$writer->process($coverage, __DIR__ . '/../../../build/log/coverage-data.php');

?>

All gathered coverage data will be put in thecoverage-data.php file.

You could load this information and combine it with other coverage information (for instance from PhpUnit) to create the output in any format you want.

The coverage logic could be put into a separate class leaving you with only two lines to add to each test you'd want to cover:

--FILE--
<?php
/* autoload classes */
require __DIR__ . '/../../../vendor/autoload.php';

cover::start;

/* run logic */
$prFiles = prFiles::getInstance()->transfer(
    $_FILES,
    __DIR__ . '/../_data/',
    'test.txt'
);
var_dump($prFiles);

cover::stop;

?>

And a cover class:

<?php

class cover
{
    private static $coverage;

    /* Setup and start code coverage */
    public static function start()
    {
        self::$coverage = new \PHP_CodeCoverage;

        /* Make sure this file is not added to the coverage data */
        $filter = self::$coverage->filter();
        $filter->addFileToBlacklist(__FILE__);

        self::$coverage->start('test');
    }

    /* stop and output coverage data */
    public static function stop()
    {
        self::$coverage->stop();

        $writer = new \PHP_CodeCoverage_Report_PHP;
        $writer->process(self::$coverage, __DIR__ . '/../build/log/coverage-data.php');
    }
}   

As the coverage logic lives outside of the PHPT file you can easily access config files or add other logic.

Potherca
  • 13,207
  • 5
  • 76
  • 94
-3

I don't specifically know why PHPUnit won't collect this coverage data for you. It might have something to do with how it uses (or doesn't use) XDebug.

You can get around this by using a test coverage tool that has no dependencies on how PHPUNit or XDebug work.

Our PHP Test Coverage will collect test coverage on any function in any PHP script it has been told to track, regardless of how that function is executed. It should have no trouble providing coverage data on executions of functions invoked by PHPT.

Ira Baxter
  • 93,541
  • 22
  • 172
  • 341
  • 3
    I doubt that it can do that, and since it's a commercial product without any public documentation, one cannot verify your claim. – cweiske Apr 18 '11 at 07:33
  • @oweiske: And your doubt is based on what technical facts or specific experience? Yes, it is a commercial product. You can download an evaluation version, read the documentation yourself, and do your own verification. – Ira Baxter Apr 18 '11 at 07:41
  • @etbal: If your tested metnod M is in another PHP script, and PHPT runs that PHP script as if it were run normally, I really believe this will work because of the way our tool instruments the code. It is true it isn't a PHPUnit fix and it that sense it isn't an answer to your question. But if you can't get an answer (because, I think, there's an interaction between the pieces you've been using), then the tools you have are broken, and you need another tool. I assume you actually do want to sovle the larger problem of, "collect coverage when I run PHPT tests", and it that sense it does solve it – Ira Baxter Apr 18 '11 at 11:13
  • 1
    I find the reaction here interesting. OP has a problem he wants solve badly enough to post a bounty. Nobody else posts any kind of answer for days. I post an answer which I believe works. It can be downloaded and tried. Yet everybody hates it because it happens to be a commercial product. – Ira Baxter Apr 21 '11 at 14:59
  • (Background for other readers: OP originally posted this question with a bounty of +100. Bounty window has expired; not awarded, ask me if I'm surprised). – Ira Baxter Apr 22 '11 at 20:34
  • @etbal: Jan, 2012. I see one answer so far. Mine. – Ira Baxter Jan 23 '12 at 23:40
  • Not what I was looking for, sorry, no help at all. – Etienne Marais Feb 09 '12 at 07:12
  • @IraBaxter I just added another answer! Only two yeas late to the game. Not too shabby :-) – Potherca Nov 10 '14 at 01:05