有時候呢,我們就是想直接為 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,因為我們可以傳入不同的物件來改變程式行為。
; read 8 bit _io_in8: ; int io_in8(int port); MOV EDX,[ESP+4] ; port (port number 只有 16 bit) MOV EAX,0 ; 把 return value 清成 0 IN AL,DX ; read 8 bit (AL) RET ; EAX 的值是 return value
_io_in16: ; int io_in16(int port); MOV EDX,[ESP+4] ; port MOV EAX,0 IN AX,DX ; read 16 bit (AX) RET
_io_in32: ; int io_in32(int port); MOV EDX,[ESP+4] ; port IN EAX,DX ; read 32 bit (EAX) RET
; write 8 bit _io_out8: ; void io_out8(int port, int data); MOV EDX,[ESP+4] ; port MOV AL,[ESP+8] ; data OUT DX,AL RET
_io_out16: ; void io_out16(int port, int data); MOV EDX,[ESP+4] ; port MOV EAX,[ESP+8] ; data OUT DX,AX RET
_io_out32: ; void io_out32(int port, int data); MOV EDX,[ESP+4] ; port MOV EAX,[ESP+8] ; data OUT DX,EAX RET
我們要設定調色板,就是找出對應 device 設定調色板的指令,然後照著做~
CLI 與 STI
CLI 是將 interrupt flag clear 為 0 的指令, STI 則是將 interrupt flag set 為 1 的指令。
interrupt flag 為 0 時,CPU 遇到 interrupt 會忽略它、不處理,flag 為 1 時 CPU 就會處理 interrupt。
EFLAGS register
EFLAGS 是由 FLAGS 16 bit 的 register 擴展而來的 32 bit register。
FLAGS 儲存 carry flag(進位 flag)與 interrupt flag 等 flag,不同 bit 代表不同 flag(有 1 個 bit 表示一個 flag 也有 2 個 bit),如下所示:
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
NT
IOPL
IOPL
OF
DF
IF
TF
SF
ZF
AF
PF
CF
IOPL 是第 12 跟 13 bit 放在一起處理。
EFLAGS 沒有 MOV 指令,只能使用 PUSHFD 跟 POPFD 來讀寫。
PUSHFD 是 push flags double-word 的縮寫,以 double word 的長度(32 bit)將 flag 的值 push 進 stack,等同 PUSH EFLAGS。POPFD 是 pop flags double-word,將 double word 長度的值從 stack pop 出來到 flag,等同 POP EFLAGS。