一分鐘懂MockObject

Posted by ayuayu on 2014/11/17

function 外部Class A 裡面有function FA()可以拿到對應值
function 外部Class B 裡面有function FB()可以拿到對應值

我寫的class:

class Example(){
    function getA(){
        return new A->FA();
    }
    function getB(){
        return new B->FB();
    }
    function mixData(){
        $dataA = getA();
        $dataB = getB();

        return $dataA + $dataB
    }
}

好了,假設這些ClassA ClassB是外部函數,也就是其他人開發的
那我們該怎測試才能確保自己的unit有正常運作呢?

class test_Example(){
    funciton test_mixData(){
        $mock = $this->getMock('Example');

        $mock->method('getA')->willReturn('1');
        $mock->method('getB')->willReturn('2');

        $this->assertEquals(3, $mock->mixData());
    }
}

一分鐘了,應該看懂在寫什麼了吧

看懂以後再看下面

在這樣的具體個案中,如果要確認Unit有沒有"正常運作"
最該care的是取得資料以後, "+ 有沒有執行,有沒有return"
而不該是"AB資料取得的值是否正確"

因此"對這個unit而言",我們不應該做類似以下事情:
"FA可能沒取到值,我們應該assert確認一下"
"FA是否是字串根本不能相加,我們要確定一下是否為浮點數"

這裡要鄭重說明一下,我們的目的終究是要確認這個unit有沒有正常運作
所以,如果你做了外來資料的過度判斷,而到時候又真的出錯了自己不在現場
如果是外來資料有問題,結果unit test檢測被其他人看到,恩經判斷期望結果不符
非常有可能會以為這是你的程式出了問題,結果查好久才查到原來根本是外部程式導致
這會造成大家的麻煩,請務必別做這樣的事情

再來,為什麼這種函數都很少去用各種condition來assert結果
因為:出問題你很難判斷是外在資料錯誤,還是你計算錯(原因可以無限上綱,不要懷疑)[註1]
與其花費大量時間寫各種判斷式來嘗試抓冰山一角
更好的方式就是不要在unit test幹這種事情(不是不測,而是別在這階段做)

(假使要做這件事情,除非程式本身就帶Exception,那我們可以做另一個測試時,帶入不合法的值來確認是否真的會觸發Exception)

Mock Object的作用就是在此,我們應該準確地確認這個unit是否真的有運作
不應該被其他外在函數的特例狀況影響到
這也是單元測試好用的地方,我們即使程式還沒完成或上線前接不到資料
也可以假設某函數資料正確來直接測試,這是Unit Test Framework最強的地方

這樣也不需要在主程式改一些測試專用Code,然後可能上線前還忘記砍掉之類

老話一句,如果想用unit test,

請絕對不要迎合unit test去多做一堆沒用判斷,然後說什麼這樣可能很難測試,我要為了unit test方便而忍痛改變現有架構之類的蠢事

請去用他對你來說真正受益,可以減少程式錯誤,增進開發效率的地方,這樣才能發揮價值

註1:
因為用少數結果絕對無法檢驗程式邏輯有沒有錯
要舉一些極端例子的話,

1.如果你assert 3應該回傳5,我程式如果為了測試,最後一行真的是寫return 5,assert下去絕對有過,但這樣的結果明顯只是鬼打牆(而且這個實際真的有人發生,因為為了寫unit test最後的return還真的忘了拿掉)

2.如果你做了兩個,assert 3應該回傳5 assert 4應該回傳 6  是否裡面剛好程式搞砸了,最後變成 input+2 ,結果assert還是有過

3.然後開始上綱,你的unit判斷式寫太複雜其實有藏了幾個BUG,結果要測目標程式測不到

沒有留言:

張貼留言