有時候呢,我們就是想直接為 private method 寫測試(任性),可能是因為我們想用測試來知道如何使用 private method,或者用 public method 來測試它實在太難太痛苦啦~
例如一個擁有商業邏輯並且會 call third-party API 的 class 做的事情是:call API 取得一包 XML 資料,parse XML 得到商業邏輯需要的資料,再做商業邏輯上的計算或操作。我們想知道 parse XML 的 private method 是否正確,但它埋在整個流程裡,而用 public method 做整個的 call API、parsing、商業邏輯的測試難以只測試到 parse XML 的部份。
所以,想為 private method 寫測試時該怎麼辦呢?
如果需要測試一個 private method,就該把它設成 public。
看到書上這句話我蠻驚恐的,想著:「等等等,不是吧?就這樣直接把 private method 變成 public 好嗎?這不會在 class 上開出看起來突兀或者不知如何使用的 method 嗎?」
如果不方便將其設為 public,大多數情況下意味著我們的 class 做太多事了,應該進行調整。
喔~原來是這樣~這倒是真的~像上面那個例子,一個 class 既 call third-party API 又 parse XML 又做商業邏輯,太多事情了。
如果我們想測試一個 private method,首先看它是不是個適合在這個 class 當作 public 的 method?如果是,直接改成 public。否則看看是不是這個 class 做太多事了,有些事可以交由另一個 class 處理。例如我們把 parse XML 有關的 method 放到另一個 parser class,這些 method 到了新 class 會變成 public,原本的 class 就能 new 一個 parser 出來做事。
classFlight{ publicfunctionsearch($searchParams) { // call third party api and get response as xml $searchResults = $this->parseSearchResponse($xml); // other impl. }
在 search() 裡先用 http client 向 third party API 送 request 並收 response,response 是一包 XML,我們想 parse 出其中需要的資料而 call parseSearchResponse()。因為 XML 相當複雜,我們希望能單獨確認 parsing 結果是否正確。
這個 class 除了 search、booking、parse 各種 XML 之外還會做許多事情,它的職責太多了,如果我們現在沒有時間去拆解它,卻想測試 parseSearchResponse() 的結果該怎麼做?
首先將 parseSearchResponse() 從 private 變成 protected:
1 2 3 4 5 6 7 8 9 10 11 12
classFlight{ publicfunctionsearch($searchParams) { // call third party api and get response as xml $searchResults = $this->parseSearchResponse($xml); // other impl. }
xUnit framework 這類型 framework 執行測試大致的作法:找到 test class 裡所有 test method(依據語言不同有不同作法,有些語言可以用 reflection),為每個 test method 產生一個單獨的 object,該 object 的任務是去執行那個 test method。利用不同 object 隔離 test case,讓 test case 不會互相影響。
例如某段程式 call 了一個 function 來計算商品價格,現在想改變計算價格的 strategy。想在不改變 call 計價 function 的情況下,改變計價結果(程式行為)。
又例如程式 call 到牽連龐大子系統的 function,我們希望在 test 中避免執行到那些複雜的 code(不然很難測或無法測),又要在 prodcution code 裡照常執行到。如果這段 code 有 seam,便能在不改動到原本 call function 的情況下,換掉該 function 的行為來避免在 test 中碰到子系統。
每個 seam 都有一個 enabling point,在這裡你可以決定使用哪種行為。
seam 是可以讓你改變程式行為的「縫隙」,enabling point 則是決定那個 seam 要是什麼行為。在 enabling point 給不同的值,可以讓 seam 有不同的行為。例如物件 seam,物件 method 的參數列表是 enabling point,因為我們可以傳入不同的物件來改變程式行為。