我不會寫測試第二蛋之我不會寫測試。俗話說 代致不是憨人想的那麼簡單。
PHPUnit Mock
在 PHPUnit 當中,有一個叫做 Mock 的東西,翻譯成中文大概可以叫做模仿。簡單來說,就是可以幫你的類產生出一個 替身,當然不是這種 替身,
好處是,你可以使用這個替身來做一些測試的事情,詳細可以上網 Google 一下,基於我對 PHPUnit 實在不熟,所以這邊就不獻寶了。
Mock with DI
Phalcon 有很多東西可以放在 DI 裡面,而,當我們要做 Functional Test 的時候,有些東西就必須要額外的傳入,舉個例子來說,
public function indexAction() {
// 我想要拿到 Request 的 get 傳入值
$params = $this->request->get();
return 'Succeeded!';
}
而 request
的來源是 \Phalcon\Http\Request
他原本就已經存在於 \Phalcon\Di\FactoryDefault()
裡面,所以本來是不需要額外設定。但是,因為你要跑的測試環境是在 CLI 底下,所以這個 request
就必須要用 Mock 來生一個替身並改寫,所以,理論上我們可以在 setUp()
裡面這樣做,
protected function setUp() {
$mock = $this->getMock('\\Phalcon\\Http\\Request', ['get']);
$mock->expects($this->any())
->method('get')
->will($this->returnValue('Succeeded!'));
$this->di->setShared('request', $mock);
}
那我們寫兩段測試,
public function test1TestCase() {
$response = $this->dispatch('/index');
$this->assertEquals('Succeeded!', $response);
}
public function test2TestCase() {
$response = $this->dispatch('/index');
$this->assertEquals('Succeeded!', $response);
}
最終執行結果會是,
疑?如果 Mock 有順利 Inject 到 $app
的話,理論上應該會拿到兩次都是成功的回應才對?為什麼第二次是 NULL
的結果?
接著我嘗試追蹤整個 setUp
的運作,他確實會把 Mock 順利注入 $app
裡面,但是當第一次成功之後,第二次的 setUp
注入卻拿不到任何返回值。
換句話說,mock
並沒有被運行。後來我將,
->will($this->returnValue('Succeeded!'));
置換成
->will($this->returnCallback(array($this, 'getMockReturnValue')));
之後,並且在 getMockReturnValue
埋入追蹤,發現一個現象,
- 只要有任何一個 TestCase 通過,這個 Mock 就會被消滅。
- 連帶的已經被埋入 DI 的
request
也會變回自己(而不是 替身 - 反之測試要是 不通過,這個 Mock 就會繼續存活下去,直到有任何人通過測試。
這不是挺弔詭的嗎?
- 我每次
setUp
都重新設定 Mock 並且重新注入 DI - 即便是把
\Phalcon\Http\Request
拿去外面重新extends
再做 Mock 一樣失敗 - 是否是 PhalconPHP 哪裡做了什麼事情?
- 或是 PHPUnit Mock 不能這樣做?
以上問題我都不會,但是找到比較傳統的解法(欸
Functional Test 帶入自訂資料參數
首先,由於我們是要從 \Phalcon\Http\Request
拿資料,但是透過 Mock 又怪怪的,那只好用傳統的方式解決,
- 寫一個 Class 叫做
mockRequest
extends \Phalcon\Http\Request
- 抄一下 Phalcon 原始碼關於
getJsonRawBody
,get
的部分 - 如果你用到更多,請多抄點
- 抄完記得改
- 加一個方法
setRequest
讓我們可以傳自訂資料進來 $this->di->setShared('request', new mockRequest());
- 在 Dispatch 的時候設定參數
- 收工
那個 Class 大概是長這樣,
class mockRequest extends \Phalcon\Http\Request
{
protected $mock_params = [];
public function setRequest(array $params = []) {
$this->mock_params = $params;
}
public function getJsonRawBody($convert = false) {
if (true === $convert) {
return $this->mock_params;
}
return json_encode($this->mock_params);
}
public function get($name = null, $filters = NULL, $defaultValue = NULL) {
if (!is_null($name)) {
if (isset($this->mock_params[$name])) {
return $this->mock_params[$name];
} else {
return null;
}
}
return $this->mock_params;
}
}
然後在 dispatch
的時候把參數餵進去,
function dispatch($url, $params) {
$params = array_merge($params, [
'key' => '你抓不到我',
'cm' => 30
]);
$request = $this->di->getShared('request');
$request->setRequest($params);
// ... 以下省略.
}
然後再跑一次測試,
然後就過了。
然後就過了。
然後就過了。
小結
我還是不會寫測試。