做者:ZGJphp
在軟工第三階段中,我完全解決了上一階段一直困擾個人文件上傳單元測試問題,在這裏作一個總結。html
注:下文介紹中,方法一方法二實現簡單但有必定的限制條件(也正由於如此我上一階段中一直未能實現文件上傳的單元測試),方法三即這一階段摸索出來的方法,廣泛性更高。前端
如下爲正文:laravel
具體見這篇博客;服務器
限制條件爲laravel版本5.4以上;cookie
具體見這篇博客;app
限制條件爲文件輸入的前端元素必須包含於form表單中,不然測試報錯;函數
以前的博客中我也提到,因爲僞造存儲和表單模擬輸入的方式均失敗,我試圖閱讀laravel底層代碼,經過構造一些信息做爲提交post請求的數據進行測試,可是各類方法均未能得到成功,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) ;
接下來強調一下務必注意的三個坑點:
注意這句代碼:
$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參數。
可見代碼中做爲$files的參數爲:
$file_arr = [
'prepare-pdf' => $pdf ,] ;
必須爲一個鍵對應着一個文件實例(文件實例屬於類UploadedFile),不可直接將一個文件實例做爲$files參數傳入。
該測試中,我直接使用了服務器上的一個pdf測試文件名做爲文件實例的文件名,因此相關測試操做也是直接對該實體文件進行操做,測試結束後將該文件還原,以確保不對網站其餘功能形成可能的破壞以及下次測試的有效性。
以上便是laravel5.1文件上傳測試的方法和須要注意的坑點,但願能幫助和我以前同樣遇到問題的程序猿朋友,固然,這篇博客的相關內容不能確保徹底正確,畢竟這些大部分是本身閱讀源碼不斷嘗試探索出的方法,若有錯誤望指正。