Laravel5.1文件上传单元测试

作者:ZGJ


在软工第三阶段中,我彻底解决了上一阶段一直困扰我的文件上传单元测试问题,在这里做一个总结。

注:下文介绍中,方法一方法二实现简单但有一定的限制条件(也正因为如此我上一阶段中一直未能实现文件上传的单元测试),方法三即这一阶段摸索出来的方法,普遍性更高。

以下为正文:

方法一:伪造存储

具体见这篇博客

限制条件为laravel版本5.4以上;

方法二:表单交互

具体见这篇博客

限制条件为文件输入的前端元素必须包含于form表单中,否则测试报错;

下面详细介绍方法三:利用call函数

​ 之前的博客中我也提到,由于伪造存储和表单模拟输入的方式均失败,我试图阅读laravel底层代码,通过构造一些信息作为提交post请求的数据进行测试,但是各种方法均未能获得成功,

​ 总结一下失败的根本原因在于:在laravel5.1中,post函数提交的数据仅相当于$_POST全局变量对Request进行实例化,然而在laravel中,hasFile函数的判定使用的是Request实例中的files变量,该变量是利用$_FILE全局变量进行实例化的

​ 但是,laravel中对HTTP请求进行单元测试的原理在于模拟一次请求执行的过程,那么就必定涉及到请求的实例化,因此也一定有files(类似于$_FILE)变量的相关初始化和使用步骤,所以这一阶段中,通过对post,call等方法的源码阅读,我找到了解决该问题的突破口。

首先找到post函数,核心部分为这一句:

$this->call('POST', $uri, $data, [], [], $server);

实则还是调用call函数,因此看到call函数:

public function call($method, $uri, $parameters = [], $cookies = [], $files = [], $server = [], $content = null)
{
$kernel = $this->app->make('Illuminate\Contracts\Http\Kernel'); $this->currentUri = $this->prepareUrlForRequest($uri); $request = Request::create(
$this->currentUri, $method, $parameters,
$cookies, $files, array_replace($this->serverVariables, $server), $content
); $response = $kernel->handle($request); $kernel->terminate($request, $response); return $this->response = $response;
}

看到call函数,令我马上眼前一亮的是其中一个参数:$files

再往下看其执行过程,这几乎就是laravel中index.php中的内容,再看其中请求的实例化:

$request = Request::create(

$this->currentUri, $method, $parameters,

$cookies, $files, array_replace($this->serverVariables, $server), $content

);

可以看到在call函数中是可以通过传入$files参数模拟文件上传的(当然还包括其他请求实例化所用的全局变量),如此强大的一个测试函数laravel官方文档中竟然只提到了parameters参数的传入而雪藏了其他的可用参数……

经过进一步的尝试,我彻底的解决了该问题,先付上该部分代码:

$pdf_info = [
'name' => public_path().'/prepare_pdf/phylab_test.pdf' ,
'error' => 0 ,
'type' => 'pdf' ,
'size' => 100000 ,
'tmp_name' => public_path().'/prepare_pdf/phylab_test.pdf' ,
] ;
$pdf = new UploadedFile($pdf_info['tmp_name'], $pdf_info['name'], $pdf_info['type'], $pdf_info['size'], $pdf_info['error'] , true);
self::assertTrue($pdf instanceof UploadedFile) ;
$file_arr = [
'prepare-pdf' => $pdf ,
] ;
$response = $this->call('POST' , '/console/uploadPre' , ['labID'=>'2134'] , [] , $file_arr);
$data = $response->getData() ;
self::assertEquals('上传成功' , $data->message) ;

接下来强调一下务必注意的三个坑点:

坑点1:UploadedFile的实例化必须带有test=true参数

注意这句代码:

$pdf = new UploadedFile($pdf_info['tmp_name'], $pdf_info['name'], $pdf_info['type'], $pdf_info['size'], $pdf_info['error'] , true);

对于UploadedFile的实例化包含6各参数,前五个很容易理解,也就是文件的五项信息,于$_FILE全局变量的结构相对应。最后一项参数不能省略,而且必须传入true,非常重要!

解释一下原因:

文件上传自然涉及到上传后文件的存储,而文件上传的存储涉及到move方法(具体控制器代码可见这篇博客

move方法中首先会利用isValid函数进行判断,如果判断失败,则会直接抛出异常。

再看isValid函数代码:

public function isValid()
{
$isOk = UPLOAD_ERR_OK === $this->error; return $this->test ? $isOk : $isOk && is_uploaded_file($this->getPathname());
}

注意返回时用到了is_upload_file函数,PHP文档中可以看到如下解释说明:

is_uploaded_file — 判断文件是否是通过 HTTP POST 上传的

如果 filename 所给出的文件是通过 HTTP POST 上传的则返回 TRUE。这可以用来确保恶意的用户无法欺骗脚本去访问本不能访问的文件,例如 /etc/passwd。

​ 这种检查显得格外重要,如果上传的文件有可能会造成对用户或本系统的其他用户显示其内容的话。

​ 为了能使 is_uploaded_file() 函数正常工作,必须指定类似于 [$_FILES'userfile']['tmp_name'] 的变量,而在从客户端上传的文件名 [$_FILES'userfile']['name'] 不能正常运作。

​ 在PHP中,文件上传后有一个暂时存储的路径,相关设置可以在php.ini文件中找到,当然我不会贸然对其进行改动。由于测试中传入的文件名均直接采用服务器上的一个测试文件,所以这个函数的判定将始终返回false。

​ 但是我们注意到,在isValid函数返回的最后,用到了其test参数,如果test参数为true,则会跳过is_upload_file函数的判定,需要注意的是,test参数默认为false,这也就是为什么对UploadedFil初始化时必须带有test=true参数。

坑点2:$files参数的结构

可见代码中作为$files的参数为:

$file_arr = [

'prepare-pdf' => $pdf ,

] ;

必须为一个键对应着一个文件实例(文件实例属于类UploadedFile),不可直接将一个文件实例作为$files参数传入

坑点3:对实体文件进行操作需谨慎

​ 该测试中,我直接使用了服务器上的一个pdf测试文件名作为文件实例的文件名,所以相关测试操作也是直接对该实体文件进行操作,测试结束后将该文件还原,以确保不对网站其他功能造成可能的破坏以及下次测试的有效性

​ 以上即是laravel5.1文件上传测试的方法和需要注意的坑点,希望能帮助和我之前一样遇到问题的程序猿朋友,当然,这篇博客的相关内容不能确保完全正确,毕竟这些大部分是自己阅读源码不断尝试探索出的方法,如有错误望指正。

【技术博客】Laravel5.1文件上传单元测试的更多相关文章

  1. 服务器文档下载zip格式 SQL Server SQL分页查询 C#过滤html标签 EF 延时加载与死锁 在JS方法中返回多个值的三种方法(转载) IEnumerable,ICollection,IList接口问题 不吹不擂,你想要的Python面试都在这里了【315+道题】 基于mvc三层架构和ajax技术实现最简单的文件上传 事件管理

    服务器文档下载zip格式   刚好这次项目中遇到了这个东西,就来弄一下,挺简单的,但是前台调用的时候弄错了,浪费了大半天的时间,本人也是菜鸟一枚.开始吧.(MVC的) @using Rattan.Co ...

  2. [技术博客] Django中文件的保存与访问

    [技术博客] Django中文件的保存与访问 在TextMarking项目开发中,数据库需要保存用户上传的文本文档. 原型设计:用户点击上传文本->保存文本->文本发送到后端保存为文件. ...

  3. [SAP ABAP开发技术总结]文本文件、Excel文件上传下传

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  4. Ajax技术——带进度条的文件上传

    1.概述 在实际的Web应该开发或网站开发过程中,经常需要实现文件上传的功能.在文件上传过程中,经常需要用户进行长时间的等待,为了让用户及时了解上传进度,可以在上传文件的同时,显示文件的上传进度条.运 ...

  5. 基于mvc三层架构和ajax技术实现最简单的文件上传

    前台页面提交文件 <!DOCTYPE html> <html><head> <meta name="viewport" content=& ...

  6. 使用Typora写博客,图片即时上传,无需第三方图床-EasyBlogImageForTypora

    背景 习惯使用markdown的人应该都知道Typora这个神器,它非常简洁高效.虽然博客园的在线markdown编辑器也不错,但毕竟是网页版,每次写东西需要登录系统-进后台-找到文章-编辑-保存草稿 ...

  7. django中博客后台将图片上传作为用户头像

    添加上传目录 # 如果不添加上传目录,仍然可以上传成功,默认为project目录,如果models.py定义了upload_to="目录名称",则会上传到"project ...

  8. laravel5.5文件上传

    /**     * 上传文件     * @param Request $request     * @return array     */    public function upload(Re ...

  9. hexo博客更新主题后上传Git操作

    克隆主题: git clone https://github.com/SuperKieran/TKL.git _config.yml文件中主题改为新增主题 # Extensions ## Plugin ...

随机推荐

  1. Redis(六)Lua脚本的支持

    Redis为什么需要Lua脚本的支持 当应用需要Redis完成一些Redis命令不支持的特性时,要么扩展Redis client或者更甚至编写c扩展Redis server.这都大大造成了应用的实现的 ...

  2. 2019-11-29-WPF-客户端开发需要知道的触摸失效问题

    原文:2019-11-29-WPF-客户端开发需要知道的触摸失效问题 title author date CreateTime categories WPF 客户端开发需要知道的触摸失效问题 lind ...

  3. FileChannel(API详解)

    1.两种获取通道的方法FileChannel.open()的方式 FileChannel channell = FileChannel.open(Paths.get("a.txt" ...

  4. Python【day 9】函数入门2

    本节内容:1. 什么是函数2. 函数定义, 函数名, 函数体以及函数的调⽤3. 函数的返回值4. 函数的参数 一.什么是函数 我们可以先去定义一个事情或者功能(接口.服务.函数.功能). 等到需要的时 ...

  5. 微服务架构 ------ DockerCompose从安装到项目部署

    DockerCompose的目的:简化Docker的启动和停止流程,以及编排Docker启动服务与服务之间的关系 DockerCompose的安装:curl -L https://get.daoclo ...

  6. loj#10172 涂抹果酱 (状压DP)

    题目: #10172. 「一本通 5.4 练习 1」涂抹果酱 解析: 三进制的状压DP 经过简单的打表发现,在\(m=5\)时最多有\(48\)种合法状态 然后就向二进制一样枚举当前状态和上一层的状态 ...

  7. JSON.stringify & JSON.parse 简析

    以前用到JSON的场景也不少,但是没有仔细的研究过,这几天趁着一个需求用到了,就整理了一下相关用法. 一. JSON.stringify() 1. 语法  JSON.stringify(value[, ...

  8. 实现HTML调用打开本地软件文件

    有时候我们想要实现一个功能,就是在HTML页面点击一个链接就能调用打开本地可执行文件.就像腾讯QQ.迅雷这种. 而实现这种功能其实也很简单,就是需要我们添加修改注册表,实现自定义URL Protoco ...

  9. Java的自动拆装箱与Integer的缓存机制

    转载请注明原文地址:https://www.cnblogs.com/ygj0930/p/10832303.html 一:基本类型与包装类型     我们知道,Java有8大基本数据类型,4整2浮1符1 ...

  10. python处理excel函数xlrd、xlwt

    https://www.jianshu.com/p/f2c9dff344c6 https://www.cnblogs.com/ilovepython/p/11068841.html 行列操作:http ...