l

2012年12月3日 星期一

對付時好時壞的測試案例(3):Asynchronous Behavior

Nov. 27 10:00~11:18
Asynchronous Behavior
今天介紹第2種造成測試案例時好時壞的問題:「Asynchronous Behavior」(非同步行為)。如下圖所示,以現在很流行的Ajax呼叫為例,當Browser透過Ajax的方式呼叫後方的某個DAO Service(資料存取服務)之後,在資料還沒有傳回Browser之前,Browser同時間還可以處理他的事情 。這種呼叫就叫做非同步的呼叫。
螢幕快照 2012-11-27 上午10.15.49

那為什麼非同步的行為會導致測試案例時好時壞呢?請參考下圖,因為呼叫者(Browser)不知道對方要執行多久才會把資料傳回來,所以在撰寫測試案例的時候,如果等待的時間太短,資料還沒有回傳,那麼測試案例就會執行失敗。
螢幕快照 2012-11-27 上午10.27.51

請看以下這段虛擬碼:
doAsyncCall();
sleep(aWhile);
readResult();
assertResult();


如果鄉民們的測試案例寫成上面這個樣子,那麼當測試案例失敗的時候,通常都是設定等待的時間(sleep)太短了,需要增加。但是,如果sleep太長,則測試案例就會跑得很慢,例如答案可能1秒就算好了,但是你卻等了60秒才去檢查,這樣也不行啊。


處理非同步行為

針對非同步行為的測試,Martin Fowler提出兩點建議:
  • 使用callback
  • 使用polling
使用callback是比較好的做法,但是需要被呼叫者配合才能夠做到。這是什麼意思?如果上圖中的DAO Service所提供的API可以允許Browser在呼叫它的時候,同時提供一個callback function(回呼函數),那麼鄉民們就可以把測試案例寫在這裡。等DAO Service傳回資料之後,自然會主動呼叫Browser所提供的callback function,這樣也就沒有「測試案例需要sleep多久再檢查回傳結果」的問題了。少數的非同步應用,例如非同步網路函數,或是最近很流行的Node.js有提供callback function。但大部分的應用可能無法使用這一招,所以只好改用樓下的這一招:polling(輪詢)。請看以下這一段虛擬碼。

doAsyncCall();
startTime = Time.now();
while(! responseReceived) {
  if (Time.now() - startTime > waitLimit) 
    throw new TestTimeoutException();
  sleep (pollingInterval);
}
readResult();
assertResult();


簡單的說,使用polling的方式,一開始先檢查資料是否已經傳回來了,如果沒有,看看是否已經等到timeout(等太久了都還沒讀到資料),如果等到timeout,則表示測試失敗,丟出一個TestTimeoutException例外。如果還沒等到timeout,則小睡片刻。採用這種方式,可以將sleep的時間設短一點,反正這次如果讀不到資料,小睡片刻之後還可以再讀一次,一直到timeout為止。如此一來可以避免一直調整sleep時間,或是將sleep設太長導致測試案例執行間太久的問題。


但是使用polling也不是完全都不需要調整時間,至少需要調整timeout的時間,因為如果timeout時間設太短,則資料還沒傳回來就已經timeout了,這樣也不行。

好難測啊

遇到這種非同步的測試,的確是滿頭大的。有一個關於測試的技巧叫做Humble Object pattern,大意是說如果鄉民們的程式邏輯處在一個不容易測試的環境當中,鄉民們就要想辦法把這個邏輯從環境中隔離(抽離)出來。換句話說,想辦法把重要的邏輯能夠用同步呼叫的方式來進行測試,那麼必須被以非同步呼叫來測試的內容或是機會就比較少了,相對地出問題的機率也會降低。

***

友藏內心獨白:有時候timeout的數值也是要調好久測試案例才會穩定啊 嚎啕大哭

沒有留言:

張貼留言