关于laravel5.1中文件上传测试的若干尝试

作者:ZGJ

版本:v1.0

PM注:本人这两天也正在尝试解决这一问题,如有进展将及时更新这一博客


在我们的软工第二阶段中,我开始着手进行后端控制器的单元测试,然而一个阶段下来,其中的一个涉及文件上传的控制器方法始终没能覆盖完全,这里总结我在此测试上进行的若干尝试(均未获得成功),先附上被测试控制器方法代码:

  1. public function uploadPreparePdf()
  2. {
  3. $exists = Auth::check() && ((Console::where('email', '=', Auth::user()->email)->get()->count()) > 0);
  4. $isAdmin = $exists;
  5. if (!$isAdmin) {
  6. return redirect('/index');
  7. }
  8. $data = ["status"=>"","message"=>""];
  9. if (Request::hasFile('prepare-pdf'))
  10. {
  11. $pdfFile = Request::file('prepare-pdf');
  12. $labID = $_POST['labID'];
  13. if (preg_match('/^pdf$/', $pdfFile->getClientOriginalExtension()) &&
  14. $pdfFile->getSize() < Config::get('phylab.maxUploadSize'))
  15. {
  16. $fname = $labID . '.pdf'; //TODO use lab id instead
  17. $pdfFile->move(Config::get('phylab.preparePath'), $fname);
  18. $data['status']=SUCCESS_MESSAGE;
  19. $data['message']="上传成功";
  20. }
  21. else
  22. {
  23. $data["status"]=FAIL_MESSAGE;
  24. $data["message"] = "上传失败,文件格式或大小不符合要求!";
  25. }
  26. }
  27. else
  28. {
  29. $data["status"]=FAIL_MESSAGE;
  30. $data["message"] = "上传失败,没有找到文件!";
  31. }
  32. return response()->json($data);
  33. }

测试失败的原因是第二个if语句hasFile()的判定始终为false……也就是这一句:

if (Request::hasFile('prepare-pdf'))

另外附上前端代码,这里有一点需要注意的是,前端文件输入的元素并未包含在form表单中,这也导致了其中一种测试方法的失败,后面在尝试2中会提及。

  1. <div class="form-group">
  2. <input type="file" id="input-prepare-pdf" name="prepare-pdf">
  3. <p class="help-block">请务必先选择一个实验,文件类型限制为PDF,大小不能超过5M</p>
  4. </div>
  5. <div class="modal-footer" style="margin-top: 0;padding-bottom: 0">
  6. <div style="float:right">
  7. <button type="submit" id="btn-upload-preview" class="btn btn-large btn-danger">上传</button>
  8. </div>
  9. </div>

以下开始详细阐述我为了解决该问题进行的若干思路和尝试:

尝试1:伪造存储

通过查阅相关资料,我了解到laravel对于文件上传测试有较为便利的原生支持,可通过“伪造”磁盘和文件的方式进行文件上传的测试,像这样:

Storage::fake('avatars');

$response = $this->json('POST', '/avatar', [

'avatar' => UploadedFile::fake()->image('avatar.jpg')

]);

然而尴尬的是,这个原生支持仅针对laravel5.4以上的版本,而我们的……则是上古版本5.1……

(要不是时间关系,我真的好想进行一次升级并重构,这5.1的版本实在是太老,无奈……)

这种最为实用而且简单的测试方法宣告失败。

尝试2:表单交互

通过查看laravel5.1的文档,可以发现文件上传测试还可以通过表单交互的方式进行,如这样:

public function testPhotoCanBeUploaded()

{

$this->visit('/upload')

->name('File Name', 'name')

->attach($absolutePathToFile, 'photo')

->press('Upload')

->see('Upload Successful!');

}

文档中对于attach和press函数的介绍是这样的:

Method Description
$this->attach($pathToFile, $elementName) "Attach" a file to the form.
$this->press($buttonTextOrElementName) "Press" a button with the given text or name.

很浅显,不多做解释,这两个函数在其他测试中我亦使用过,然而在这里进行测试时,会产生如下报错:

报错行显示在press函数,原因是前端文件输入并未包含在form表单中(见上文中的前端代码),但是由于对前端修改还需要牵扯到其他的一些部分,何况我认为为了方便测试而修改代码并不是一个好习惯(个人理解)。

所以这一种尝试也宣告失败……

以上两种测试是通过查阅资料和阅读官方文档能找到的两种主流测试方法,未能成功,不甘失败的我开始围绕Request::hasFile()方法阅读laravel5.1的底层代码,希望通过其他途径解决该问题:

尝试3:修改预定义全局变量$_FILE

为什么进行这一项尝试,这就涉及到laravel请求生命周期的相关知识。

这里仅阐述请求实例化的部分,laravel中请求实例化位于文件Illuminate\Http\Request.php中

  1. /**
  2. * Create a new Illuminate HTTP request from server variables.
  3. *
  4. * @return static
  5. */
  6. public static function capture()
  7. {
  8. static::enableHttpMethodParameterOverride();
  9. return static::createFromBase(SymfonyRequest::createFromGlobals());
  10. }

而通过createFromBase方法的代码(这里就不贴了)又能发现laravel框架的请求实例是在Symfony请求实例的基础上创建的。

对此,我又找到vendor\symfony\http-foundation\Request.php文件

其中关于请求的实例化有一句关键代码:

  1. $request = self::createRequestFromFactory($_GET, $_POST, array(), $_COOKIE, $_FILES, $server);

也就是说,这是通过PHP的全局变量创建一个新的请求实例,如果没有laravel框架,PHP对于文件上传以及测试可以直接操作$_FILE全局变量是肯定没有问题的,laravel对其封装在Request实例中,故我想到可否在发送post请求之前修改$_FILE,即这样:

(多提及一点,$_FILE全局变量的结构是一个二维数组,每一个元素有五个字段,具体可查阅相关PHP文档)

  1. $pdf_info = [
  2. 'name' => public_path().'/prepare_pdf/phylab_test.pdf' ,
  3. 'error' => 0 ,
  4. 'type' => 'pdf' ,
  5. 'size' => 100000000 ,
  6. 'tmp_name' => public_path().'/prepare_pdf/phylab_test.pdf' ,
  7. ] ;
  8. $_FILES["prepare-pdf"]["name"] = $pdf_info['name'] ;
  9. $_FILES["prepare-pdf"]['error'] = $pdf_info['error'] ;
  10. $_FILES["prepare-pdf"]['type'] = $pdf_info['type'] ;
  11. $_FILES["prepare-pdf"]['tmp_name'] = $pdf_info['tmp_name'] ;
  12. $_FILES["prepare-pdf"]['size'] = $pdf_info['size'] ;
  13. //Request::capture() ;
  14. //self::assertTrue(Request::hasFile('prepare-pdf')) ;
  15. $this->post('/console/uploadPre')
  16. ->seeJson([

为了验证之前对于请求实例化代码的理解,可见当中有两句注释代码:

  1. //Request::capture() ;
  2. //self::assertTrue(Request::hasFile('prepare-pdf')) ;

这两句代码(取消注释后)是可以正常通过测试的。

遗憾的是,测试中提交post请求后,后端控制器方法中fasFile()的判定依旧为false。

这一种尝试,失败……

尝试4:构造SplFileInfo类的实例

既然测试始终无法覆盖完全的原因是hasFile方法判断为false,那我就看看这一个函数是如何进行的,故我找到Request.php中hasFile方法的源码:

  1. public function hasFile($key)
  2. {
  3. if (! is_array($files = $this->file($key))) {
  4. $files = [$files];
  5. }
  6. foreach ($files as $file) {
  7. if ($this->isValidFile($file)) {
  8. return true;
  9. }
  10. }
  11. return false;
  12. }

首先我看到的是返回条件即$this->isValidFile($file)这一句:

在此找到isValidFile()这个方法:

  1. protected function isValidFile($file)
  2. {
  3. return $file instanceof SplFileInfo && $file->getPath() != '';
  4. }

所以我想到了构造SplFileInfo类的实例并作为post请求的数据发送一个post请求:

  1. $pdf = new SplFileInfo('prepare_pdf/phylab_test.pdf') ;
  2. $this->post('/console/uploadPre' , [
  3. 'prepare-pdf' => $pdf ,
  4. ])->seeJson([

很遗憾,这一种尝试依旧失败……

尝试5:构造UploadedFile类的实例

先提及一句,Request::file 方法返回来的对象是 Symfony\Component\HttpFoundation\File\UploadedFile 类的一个实例,这个类继承了 PHP 的 SplFileInfo 类,当然进行这个尝试更重要的原因在后面:

在上一项尝试中,我发现hasFile()方法在进行返回判断之前,有这样一句代码:

$files = $this->file($key)

找到file()方法:

  1. public function file($key = null, $default = null)
  2. {
  3. return Arr::get($this->files->all(), $key, $default);
  4. }

在返回调用Arr::get方法时第一个参数比较重要,即$this->files->all() ,

这里又需要了解Request实例中的$files属性,找到其源头,位于文件vendor\symfony\http-foundation\Request.php中,而$file的初始化是这样的:

  1. $this->files = new FileBag($files);

传入的参数即为PHP中的$_FILE全局变量,这也就和我前面所说的请求实例化联系到了一起,对此,我再次找到FileBag类的源码,其中对于FileBag类的构造进行了多层调用,一层层找到其根源,有这样一句:

  1. parent::set($key, $this->convertFileInformation($value));

而通过对convertFileInformation()函数的解读:

  1. protected function convertFileInformation($file)
  2. {
  3. if ($file instanceof UploadedFile) {
  4. return $file;
  5. }
  6. $file = $this->fixPhpFilesArray($file);
  7. if (is_array($file)) {

可以发现,这个函数接收的参数有两种,一种是UploadedFile实例,另一种是一个文件信息的数组,并通过这个数组创建一个UploadedFile实例:

  1. $file = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['size'], $file['error']);

这也就和$_FILE全局变量的结构联系到了一起。

通过改变$_FILE的方式前面已经试过,失败,那么我能不能通过传入UploadedFile实例的方式提交post请求,即这样:

  1. $pdf = new UploadedFile($pdf_info['tmp_name'], $pdf_info['name'], $pdf_info['type'], $pdf_info['size'], $pdf_info['error']);
  2. $this->post('/console/uploadPre' , [
  3. 'prepare-pdf' => $pdf ,
  4. ])->seeJson([

失望的是,这种尝试,仍然失败……

以上便是我针对laravel5.1文件上传进行的几项主要尝试的过程,当然实际上还有一些看似就不靠谱的小尝试(抱着幻想还是要试一下),那些就不拿出来献丑了哈哈…

虽然有一些综合的原因,但是终究还是自己学术不精。如果有大佬知道解决的方案,或者是我以上部分的理解存在一些问题,欢迎指正,感激不尽!

【技术博客】 关于laravel5.1中文件上传测试的若干尝试的更多相关文章

  1. 【技术博客】Laravel5.1文件上传单元测试

    Laravel5.1文件上传单元测试 作者:ZGJ 在软工第三阶段中,我彻底解决了上一阶段一直困扰我的文件上传单元测试问题,在这里做一个总结. 注:下文介绍中,方法一方法二实现简单但有一定的限制条件( ...

  2. 【技术博客】Postman接口测试教程 - 环境、附加验证、文件上传测试

    Postman接口测试教程 - 环境.附加验证.文件上传测试 v1.0 作者:ZBW 前言 继利用Postman和Jmeter进行接口性能测试之后,我们发现Postman作为一款入门容易的工具,其内置 ...

  3. C# 之 FTPserver中文件上传与下载(二)

            通过上一篇博客<C# 之 FTPserver中文件上传与下载(一)>,我们已经创建好了一个FTPserver,而且该server须要username和password的验证 ...

  4. ASP.NET:MVC中文件上传与地址变化处理

    目录 文件的上传和路径处理必须解决下面列出的实际问题: 1.重复文件处理 2.单独文件上传 3.编辑器中文件上传 4.处理文章中的图片路径 5.处理上传地址的变化 一.上传文件和重复文件处理 文件处理 ...

  5. jsp\struts1.2\struts2 中文件上传(转)

    jsp\struts1.2\struts2 中文件上传 a.在jsp中简单利用Commons-fileupload组件实现 b.在struts1.2中实现c.在sturts2中实现现在把Code与大家 ...

  6. easyui-dialog中文件上传处理

    function openDialog() { // $('#dlg').dialog('open'); //EasyUi的dialog中文件上传,后台获取不到文件,需要改写为下面这样 $(" ...

  7. layUI框架中文件上传前后端交互及遇到的相关问题

    下面我将讲述一下我在使用layUI框架中文件上传所遇到的问题: 前端jsp页面: <div class="layui-form-item"> <label cla ...

  8. JFinal中文件上传后会默认放置到WebContent的upload包下,但是tomcat会自动重启,当我们再次打开upload文件夹查看我们刚刚上传的文件时,发现上传的文件已经没有了。

    JFinal中文件上传后会默认放置到WebContent的upload包下,但是tomcat会自动重启,当我们再次打开upload文件夹查看我们刚刚上传的文件时,发现上传的文件已经没有了.因为tomc ...

  9. Java中文件上传路径与路径修改相关问题(tomcat8.0+eclipse)

    1.普通文件上传的路径: 通过getRealPath获取相关路径 String photoFolder =request.getServletContext().getRealPath("u ...

随机推荐

  1. 给基于对话框的MFC程序添加状态栏并实时显示时间

    转载自丝雪儿 1.首先在string table 里添加两个字串,ID分别为IDS_INDICATOR_MESSAGE and IDS_INDICATOR_TIME 2.在你的 dlg.h 类里面加个 ...

  2. 走一次HashMap的存取

    忘了太多东西,好好复习. 存: if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length;//检查 ...

  3. 学习笔记之操作系统(Operating System)

    学习笔记之多线程 - 浩然119 - 博客园 https://www.cnblogs.com/pegasus923/p/5554565.html 用三个线程按顺序循环打印ABC三个字母 - 浩然119 ...

  4. vue项目打包采坑

    1. vue项目打包采坑 1.1. vue运行报错error:Cannot assign to read only property 'exports' of object '#' 这个错误我是在打包 ...

  5. idea注释类,方法

    1.添加类注释:file-settings-file and code Templates-Class #if (${PACKAGE_NAME} && ${PACKAGE_NAME} ...

  6. Django 之 Form 组件

    常用功能 From 组件主要有以下几大功能: 生成 HTML 标签 验证用户数据(显示错误信息) HTML Form 提交保留上次提交数据 初始化页面显示内容 小试牛刀 下面我们通过 Form 组件来 ...

  7. JAVA构造器,重载与重写

    1. java构造器 构造器也叫构造方法(constructor), 用于对象初始化. 构造器是一个创建对象时被自动创建的特殊方法,目的是对象的初始化. 构造器 的名称与类的名称一致. JAVA通过n ...

  8. seaborn(2)---画分类图/分布图/回归图/矩阵图

    二.分类图 1. 分类散点图 (1)散点图striplot(kind='strip') 方法1: seaborn.stripplot(x=None, y=None, hue=None, data=No ...

  9. 4484: [Jsoi2015]最小表示(拓扑序+bitset维护连通性)

    4484: [Jsoi2015]最小表示 题目链接 题解: bitset的题感觉都好巧妙啊QAQ. 因为题目中给出的是一个DAG,如果\(u->v\)这条边可以删去,等价于还存在一个更长的路径可 ...

  10. 安卓QQ聊天记录导出、备份完全攻略

    发到知乎竟然被删掉,我也不知道我到底违反了哪条.唉,别人的毕竟是别人的.虽然博客园也是别人的 前言 我对聊天记录的备份比较执着,也在这上面折腾过不少.碰到过不少令人头疼的麻烦,在这里分享一下经验. 关 ...