這篇來談一種常見情境:呼叫的方法不回傳值,要怎麼驗證它「真的有做事」?
Query 與 Command#
程式行為大致可分為兩類:
- Query:向某個物件詢問東西。例如向
Student問 fullName、向Transcript問 averageScore。 - Command:要某個物件去做事。例如要
Student去 register、要Semester去 start、要EmailService去 send。
物件導向的核心思考之一是:現實怎麼做,程式就怎麼寫。生活中委託別人辦事,有時對方會回報執行情形,有時則「有事再回報」。在 Command 的設計上,常見的回應方式包括:什麼都不回、回個 boolean 表示成功與否、回個 int 表示影響筆數,再不然就是出錯時丟例外(Exception)。
Tell-Don’t-Ask#
物件導向有句設計原則:「Tell. Don’t Ask.」就是在強調這種風格。當我們把事情委託出去,就應該專注在「告訴對方該做什麼」,而不是先問東問西、再自己處理。
Tell-Don’t-Ask 帶來的副作用是:如果方法不回傳值,從外部看根本不知道內部到底做了什麼。這就需要新的測試手段——驗證行為而不是驗證回傳值。
如果受測方法至少會回個值,事後可以驗證那個值;但若是 void,就要改變思路:去檢查它有沒有實際呼叫某個依賴的方法、用什麼參數、被呼叫了幾次。這就是行為測試(Behavior Verification)的精神,與檢查回傳值的狀態測試(State Verification)不同。
不回傳的 Command 範例#
延續獎學金的情境:當申請通過時間檢查後,就把申請單存進資料庫。若這是唯一條件,程式可以這樣寫:
public class ApplyScholarshipService {
private final ApplicationChecker checker;
private final ApplicationRepository applicationRepository;
public ApplyScholarshipService(ApplicationChecker checker, ApplicationRepository applicationRepository) {
this.checker = checker;
this.applicationRepository = applicationRepository;
}
public void apply(Application application) {
if (this.checker.checkTime(application)) {
this.applicationRepository.create(application);
}
}
}邏輯非常單純:「checker 通過 ➡️ 叫 repository 把申請單存起來」。
用 verify 驗證行為#
checker 因為涉及時間不容易控制,可以用 Mockito 讓它依需求回傳 true 或 false。當 checker 回 true 時,repository.create() 沒有回傳值,所以要驗證的是「它有沒有被呼叫一次」。在 Mockito 中可以使用 verify:
@Test
void check_ok_then_create() {
Application application = new Application(777L);
ApplicationChecker checker = mock(ApplicationChecker.class);
when(checker.checkTime(application)).thenReturn(true);
ApplicationRepository repository = mock(ApplicationRepository.class);
new ApplyScholarshipService(checker, repository).apply(application);
verify(repository, times(1)).create(application);
}這個測項證明「一旦時間檢查通過,service 確實會請 repository 建立一次申請紀錄」。
反向情境#
下一個自然要驗的情境是「checker 回傳 false 時,不應該呼叫 create」。寫法大同小異,差別只在最後改成驗證 create 從未被呼叫,例如:
verify(repository, times(0)).create(application);或使用 verify(repository, never()).create(any())。透過這兩個測項,無論 checker 結果為何,service 的行為都會被約束住。
原文出處#
- 原書/iThome:https://ithelp.ithome.com.tw/articles/10260724