PHPUnit测试二进制数据上传

时间:2012-01-28 17:12:44

标签: php file-upload upload phpunit plupload

如何使用二进制数据填充php://input以测试上传? (或以其他方式测试分块上传)。我使用plupload作为我的前端,我想对我的后端进行单元测试。

这是我要测试的代码段:

public function recieve($file = 'file')
{
    // Get parameters
    $chunk = isset($_REQUEST["chunk"]) ? intval($_REQUEST["chunk"]) : 0;
    $chunks = isset($_REQUEST["chunks"]) ? intval($_REQUEST["chunks"]) : 0;
    $fileName = isset($_REQUEST["name"]) ? $_REQUEST["name"] : '';
    $targetDir = $this->_uploadDir;

    // Clean the fileName for security reasons
    $fileName = preg_replace('/[^\w\._]+/', '_', $fileName);

    // Make sure the fileName is unique but only if chunking is disabled
    if ($chunks < 2 && file_exists($targetDir . DIRECTORY_SEPARATOR . $fileName)) {
        $ext = strrpos($fileName, '.');
        $fileName_a = substr($fileName, 0, $ext);
        $fileName_b = substr($fileName, $ext);

        $count = 1;
        while (file_exists(
                $targetDir . DIRECTORY_SEPARATOR . $fileName_a . '_' . $count . $fileName_b)) {
            $count++;
        }

        $fileName = $fileName_a . '_' . $count . $fileName_b;
    }

    $filePath = $targetDir . DIRECTORY_SEPARATOR . $fileName;

    // Create target dir
    if (!file_exists($targetDir)) {
        if (!is_writable(dirname($targetDir))) {
            $this->_messages[] = 'Cannot write to ' . dirname($targetDir) . ' for mkdir';
            return false;
        }
        mkdir($targetDir, 0777, true);
    }

    // Check permissions
    if (!is_writable($targetDir)) {
        $this->_messages[] = 'Unable to write to temp directory.';
        return false;
    }

    // Look for the content type header
    $contentType = null;
    if (isset($_SERVER["HTTP_CONTENT_TYPE"]))
        $contentType = $_SERVER["HTTP_CONTENT_TYPE"];

    if (isset($_SERVER["CONTENT_TYPE"]))
        $contentType = $_SERVER["CONTENT_TYPE"];

    // Handle non multipart uploads older WebKit versions didn't support multipart in HTML5
    if (strpos($contentType, "multipart") !== false) {
        if (isset($_FILES[$file]['tmp_name']) && is_uploaded_file($_FILES[$file]['tmp_name'])) {
            // Open temp file
            $out = fopen("{$filePath}.part", $chunk == 0 ? "wb" : "ab");
            if ($out) {
                // Read binary input stream and append it to temp file
                $in = fopen($_FILES[$file]['tmp_name'], "rb");

                if ($in) {
                    while ($buff = fread($in, 4096)) {
                        fwrite($out, $buff);
                    }
                } else {
                    $this->_messages[] = 'Failed to open input stream.';
                    return false;
                }
                fclose($in);
                fclose($out);
                unlink($_FILES[$file]['tmp_name']);
            } else {
                $this->_messages[] = 'Failed to open output stream.';
                return false;
            }
        } else {
            $this->_messages[] = 'Failed to move uploaded file.';
            return false;
        }
    } else {
        // Open temp file
        $out = fopen("{$filePath}.part", $chunk == 0 ? "wb" : "ab");
        if ($out) {
            // Read binary input stream and append it to temp file
            $in = fopen("php://input", "rb");
            if ($in) {
                while ($buff = fread($in, 4096)) {
                    fwrite($out, $buff);
                }
            } else {
                $this->_messages[] = 'Failed to open input stream.';
                return false;
            }
            fclose($in);
            fclose($out);
        } else {
            $this->_messages[] = 'Failed to open output stream.';
            return false;
        }
    }

    // Check if file upload is complete
    if (!$chunks || $chunk == $chunks - 1) {
        // Strip the temp .part suffix off
        rename("{$filePath}.part", $filePath);
        return $filePath;
    }
}

*修改

添加了更多代码,以显示我想要进行单元测试的内容

2 个答案:

答案 0 :(得分:6)

似乎这不能通过常规的PHPUnit测试来完成,但我找到了一种方法来将.phpt测试与PHPUnit集成在:http://qafoo.com/blog/013_testing_file_uploads_with_php.html

供参考,uploadTest.phpt:

--TEST--
Example test emulating a file upload
--POST_RAW--
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryfywL8UCjFtqUBTQn

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

Contents of text file here

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

Upload
------WebKitFormBoundaryfywL8UCjFtqUBTQn--
--FILE--
<?php
require __DIR__ . '/Upload.php';

$upload = new Upload();
$file = $upload->recieve('file');

var_dump(file_exists($file));
?>
--EXPECT--
bool(true)

相应的PHPUnit测试集成:

<?php
require_once 'PHPUnit/Extensions/PhptTestCase.php';
class UploadExampleTest extends PHPUnit_Extensions_PhptTestCase
{
    public function __construct()
    {
        parent::__construct(__DIR__ . '/uploadTest.phpt');
    }
}

答案 1 :(得分:2)

首先,如果不是单一的200行方法,你会发现这个代码更容易进行单元测试!单位越小 - 测试越小。您可以提取getFileName()getContentType()isChunked()getChunkDetails()transferChunk()等。其中许多方法都很短,可让您彻底测试无需设置整个上传。这是一个例子,getContentType()

public function getContentType() {
    if (isset($_SERVER["CONTENT_TYPE"]))
        return $_SERVER["CONTENT_TYPE"];

    if (isset($_SERVER["HTTP_CONTENT_TYPE"]))
        return $_SERVER["HTTP_CONTENT_TYPE"];

    throw new FileTransferException('Unknown content type');
}

这种方法的测试很简单。

/**
 * @expectedException FileTransferException
 */
public function testUnknownContentType() {
    $fixture = new FileTransfer();
    unset($_SERVER["CONTENT_TYPE"]);
    unset($_SERVER["HTTP_CONTENT_TYPE"]);
    $fixture->getContentType();
}

public function testRegularContentType() {
    $fixture = new FileTransfer();
    $_SERVER["CONTENT_TYPE"] = 'regular';
    unset($_SERVER["HTTP_CONTENT_TYPE"]);
    self::assertEquals('regular', $fixture->getContentType());
}

public function testHttpContentType() {
    $fixture = new FileTransfer();
    unset($_SERVER["CONTENT_TYPE"]);
    $_SERVER["HTTP_CONTENT_TYPE"] = 'http';
    self::assertEquals('http', $fixture->getContentType());
}

public function testRegularContentTypeTakesPrecedence() {
    $fixture = new FileTransfer();
    $_SERVER["HTTP_CONTENT_TYPE"] = 'http';
    $_SERVER["CONTENT_TYPE"] = 'regular';
    self::assertEquals('regular', $fixture->getContentType());
}

一旦使用简单的东西重构代码,就可以将所有I / O处理提取到一个单独的类中。通过这样做,您可以在测试非I / O代码时使用模拟对象,这意味着您不必依赖实际文件或使用伪数据填充php://input。这是“单元测试”的“单元”部分:将代码分解为可测试的小单元,并在可行的情况下从等式中删除其他单元。

在提取的I / O处理类中,将调用放到is_uploaded_file()并将输入流打开到不同的方法中,例如isUploadedFile()openInputStream()。在测试时,您可以模拟这些方法,而不是模拟其底层机制。测试is_uploaded_file()在单元测试中的作用是没有意义的。这是PHP的责任,您可以在集成(端到端)测试中验证一切是否正常工作。

这将减少对I / O代码的测试。此时,您可以使用测试文件夹中的真实文件或vfsStream等包。