PHPUnit测试二进制数据上传
如何使用二进制数据填充 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;
}
}
*编辑:
添加了更多代码,以显示我想要进行单元测试的内容
How can I populate php://input
with binary data in order to test uploads? (Or otherwise test chunked uploads). I am using plupload as my frontend, and I want to unittest my backend.
This is the piece of code I want to test:
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;
}
}
*Edit:
Added more code, to show what I want to unit test
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
似乎这无法通过常规 PHPUnit 测试来完成,但我找到了一种将 .phpt 测试与 PHPUnit 集成的方法: http://qafoo.com/blog/013_testing_file_uploads_with_php.html
供参考,uploadTest.phpt:
以及相应的PHPUnit测试集成:
Seems this can't be done with regular PHPUnit tests, but I found a way to integrate .phpt tests with PHPUnit at: http://qafoo.com/blog/013_testing_file_uploads_with_php.html
For reference, uploadTest.phpt :
And corresponding PHPUnit test integration:
首先,您会发现如果这段代码不是一个 200 行的方法,那么它更容易进行单元测试!单位越小——测试就越小。您可以提取
getFileName()
、getContentType()
、isChunked()
或getChunkDetails()
、其中许多方法都非常简短,允许您彻底测试它们,而无需设置整个上传。下面是一个示例,
getContentType()
:此方法的测试非常简单。
一旦您使用简单的内容重构了代码,您就可以将所有 I/O 处理提取到一个单独的类中。通过这样做,您可以在测试非 I/O 代码时使用模拟对象,这意味着您不必依赖实际文件或用虚假数据填充
php://input
。这是“单元测试”的“单元”部分:将代码分解为小的、可测试的单元,并在可行的情况下从等式中删除其他单元。在提取的 I/O 处理类中,调用
is_uploaded_file()
并将输入流打开到单独的方法中,例如isUploadedFile()
和openInputStream( )
。在测试时,您可以模拟这些方法,而不是模拟它们的底层机制。在单元测试中测试is_uploaded_file()
是否有效是没有意义的。这是 PHP 的责任,您可以在集成(端到端)测试中验证一切是否按预期工作。这会将 I/O 代码的测试减少到最低限度。此时,您可以使用测试文件夹中的真实文件或 vfsStream 之类的包。
First, you'd find this code significantly easier to unit test if it weren't a single 200 line method! The smaller the unit--the smaller the test. You could extract
getFileName()
,getContentType()
,isChunked()
orgetChunkDetails()
,transferChunk()
, etc. Many of these methods would be very short and allow you to test them thoroughly without having to set up an entire upload. Here's one example,getContentType()
:The tests for this method are straight-forward.
Once you've refactored the code with the easy stuff, you can extract all of the I/O handling into a separate class. By doing so you can use a mock object when testing the non-I/O code, meaning you won't have to rely on actual files or stuffing
php://input
with fake data. This is the "unit" part of "unit testing": breaking your code up into small, testable units, and removing the other units from the equation where practical.In the extracted I/O-handling class, place the calls to
is_uploaded_file()
and opening the input stream into separate methods, e.g.isUploadedFile()
andopenInputStream()
. While testing you can mock those methods instead of mocking their underlying mechanisms. There's no point in testing thatis_uploaded_file()
works in a unit test. That's PHP's responsibility, and you can verify everything works as expected in an integration (end-to-end) test.This will reduce testing your I/O code to the bare minimum. At that point you can use real files in your tests folder or a package like vfsStream.