【问题标题】:PHPUnit test binary data uploadsPHPUnit 测试二进制数据上传
【发布时间】:2012-02-21 06:15:34
【问题描述】:

如何使用二进制数据填充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;
    }
}

*编辑

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

【问题讨论】:

  • 你想单元或集成测试这个吗?如果你想集成测试它:到什么程度?包括通过网络服务器与否? $_REQUEST 似乎建议进行集成测试,但我想在回答之前先问一下。
  • 我想对接收上传的服务器代码进行单元测试,以验证任何上传的文件都会得到预期的处理
  • 很抱歉再次询问,但我没有关注。您可以以太 UNIT 测试这段代码或测试通过 Web 服务器上传的文件,这两件事在语义上都没有意义。如果你想上传一个集成测试的“假”文件。也许只是文字是这里的问题? stackoverflow.com/questions/516572/…
  • 我想对它进行单元测试。其余的 php 代码正在检查上传是否有效(content_length 等),它正在检查它是否是分块上传,以及它是否是多部分的。我想验证代码是否准确区分了不同的场景,并相应地对待它们——因此我想通过单元测试(而不是通过集成测试的实际浏览器行为)使用假数据来模仿浏览器行为(和不当行为)。

标签: php file-upload upload phpunit plupload


【解决方案1】:

首先,如果这段代码不是一个 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 之类的包。

【讨论】:

  • 虽然我同意你的观点,一般来说这是很好的建议,但它们并没有解决我关于单元测试文件上传的实际问题。特别是“is_uploaded_file”,而 php://input 在我的代码中将失败。在我看来,测试这一点的唯一方法是使用 .phpt 文件,因为据我所知,无法模拟 is_uploaded_file 和 php://input。
  • 我添加了倒数第二段,希望能澄清一点。关键是您不会调用is_uploaded_file() 或使用php://input。而是模拟您自己的 isUploadedFile()openInputStream() 方法。
  • 在我看来,您不应该为了解决单元测试问题而更改实现。 :)
  • 我会在不考虑单元测试的情况下进行所有上述更改,保存一个:提取对is_uploaded_file()的调用。为此,您别无选择,因为您无法在没有扩展的情况下在 PHP 中模拟函数。
【解决方案2】:

这似乎无法通过常规 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');
    }
}

【讨论】:

  • 这非常适合集成测试。
  • 似乎无法正常工作。首先*.phpt 测试执行需要--cgi 选项,因为GETPOST 在测试中的使用。还有文档。声明,Currently only available with server-tests.php,我不知道在哪里寻找它以及它如何连接到我的测试。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-10-15
  • 2011-06-02
  • 2011-11-23
  • 2016-04-25
  • 2014-08-02
  • 2011-05-08
相关资源
最近更新 更多