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. c#多个按钮执行同一类事件-按钮按下和弹起

    首先在Winform中添加一个Button控件,在属性里面为控件添加鼠标按下和弹起事件(不要双击按钮,在属性里面添加) 再添加其他几个按钮控件,在控件的属性里面为鼠标按下和弹起添加已定义好处理函数(M ...

  2. 将本地代码提交到github上

    首先点击new,创建一个github仓库. 设置仓库名称. 这时候你需要创建一个ssh密匙,桌面右键Git Bash Here.敲入命令.cd ~/.ssh/,如果提示"no such&qu ...

  3. c#语法复习总结(2)-数据类型

    C#数据类型可以分值类型和引用类型.值类型,先说说一个概念 c#栈和堆. 一,栈和堆. 堆:在c里面叫堆,在c#里面其实叫托管堆.为什么叫托管堆,我们往下看. 栈:就是堆栈,因为和堆一起叫着别扭,就简 ...

  4. sql server: 数据库备份时出现-operating-system-error-5拒绝访问

    本文转自:https://blog.csdn.net/ibsfn/article/details/80770855 sql-server 数据库备份时出现-operating-system-error ...

  5. 使用Nginx反向代理Docker的Asp.Net Core项目的请求

    承接上文的对Kestrel的思考 上一篇介绍了如何一下在docker中发布Asp.Net Core项目(传送门)在最后尝试从外网访问网站的时候发现请求的响应头中包含了这个信息Server:Kestre ...

  6. 关于django模型里面的__str__和__unicode__

    python3    django模型里面 使用 __str__  如果使用__unicode__是无效的 简而言之,就是__str__和__unicode__都是为了再管理站点中加载这个表时想显示什 ...

  7. zsh禁用自动更新

    编辑.oh-my-zsh/oh-my-zsh.sh文件 set DISABLE_AUTO_UPDATE = false # Check for updates on initial load... i ...

  8. javascript中的12种循环遍历方法1

    1:for循环 let arr = [1,2,3]; for(let i =0;i<arr.length;i++){ console.log(i,arr[i]) } //for循环是js中最常用 ...

  9. getOwnPropertyDescriptor

    语法 Object.getOwnPropertyDescriptor(obj, prop) 参数 obj 需要查找的目标对象 prop 目标对象内属性名称 返回值 如果指定的属性存在于对象上,则返回其 ...

  10. CSS泣鬼神

    博主网站 一.CSS介绍和语法 CSS(Cascading Style Sheet,层叠样式表)定义如何显示HTML元素. 每个CSS样式由两个组成部分:选择器和声明.声明又包括属性和属性值.每个声明 ...