套件呼叫 ffmpeg 或 git 等外部命令時,使用端應用程式的環境差異與失敗會直接成為套件的行為。不要直接呼叫 shell_exec() 或 exec(),而是透過 Process facade 執行,並在測試中以 Process::fake() 取代。
本頁以 Laravel 13 為對象,確認了 Framework v13.42.0 的實作。前提是已了解程序與套件的測試。這裡介紹的陣列命令比對,以 Process 內部使用的 Symfony Process 的引數轉換為前提。
避免直接執行的理由
直接呼叫函式無法在測試中替換,逾時與結束碼的處理也會因呼叫端而各不相同。透過 Process facade,可以統一執行紀錄、fake、逾時以及失敗時的例外。
以陣列傳遞命令
字串命令會經過 sh 解析,若包含使用者輸入,就會產生引號問題與注入的可能。在套件內部請以陣列傳遞引數。
PendingProcess 的預設逾時為 60 秒,閒置逾時為 null。耗時較長的轉換請以 timeout() 明確指定;對於輸出可能停滯的處理,請同時使用 idleTimeout()。
失敗時的例外與訊息
當結束碼不代表成功時,ProcessResult::throw() 會拋出 ProcessFailedException。訊息中包含所執行的命令、結束碼,以及標準輸出與標準錯誤輸出。
若把機密資訊放進命令引數,它會被包含在 ProcessFailedException 的訊息中,可能出現在日誌或畫面上。不要把權杖等作為引數傳遞,而是透過 env() 傳遞,或寫入命令的設定檔。
逾時以 ProcessTimedOutException 表示,閒置逾時以 ProcessIdleTimedOutException 表示。請向套件的使用者明確說明會拋出哪些例外,以及捕捉時可以期待什麼。
在測試中 fake
在測試中以 Process::fake() 取代外部命令。Process::result() 可以指定標準輸出、標準錯誤與結束碼。
向 assertRan() 傳入閉包,可以確認 PendingProcess 的 command、timeout、environment 等。陣列命令會與 $process->command 進行完全一致比較,因此引數順序或值的變更會在測試中被偵測出來。
陣列命令的 fake 鍵
Process 內部使用 Str::is() 將 fake() 的鍵與 getCommandline() 的結果進行比對。陣列命令會被轉換為適用於 shell 的帶引號字串,因此 'ffmpeg*' 這類字串模式不會相符。在陣列命令的測試中,請使用以下其中一種方式。
防止非預期的外部命令
要偵測測試中不應執行的外部命令,請使用 preventStrayProcesses()。與 fake 不相符的命令會拋出 RuntimeException,並不會實際啟動。
使用注入的 Factory 時
若套件的建構子接收 Illuminate\Process\Factory,請確認 Process::fake() 的狀態能否傳到注入目標。Facade 會快取已解析的實例,swap() 會替換容器中的實例。另一方面,Factory 是否在容器中共用,無法依賴框架的明確註冊,因此請在測試中實際確認。
若無法傳到注入目標,請在測試中明確替換 Factory,或改為經由 Process facade 的設計。無論哪種情況,以 assertRan() 確認呼叫已被記錄,都能發現遺漏的替換。
為長期維護追加的驗證
參考的一手資料