iT邦幫忙

2021 iThome 鐵人賽

DAY 7
2
Software Development

你就是都不寫測試才會沒時間:Kuma 的 30 天 Unit Test 手把手教學,從理論到實戰 (Java 篇)系列 第 7

Day 07 「Tell. Don't Ask.」 測試與依賴:測行為

2021 IT 鐵人 Day 07 測試與依賴:測行為

今天來聊「不回傳值的命令」的使用場景與測試。

Query 與 Command

程式行為,大多不外乎 Query 或 Command。所謂的 Query,就是向某個物件問東西,譬如向 Student 問 FullName、向 Transcript 問 AverageScore、向 Course 問 Description 等等。而 Command 則是叫物件去做某件事。譬如叫 Student 去 Register、叫 Semester 去 Start、叫 EmailService 去 Send 等等。

私以為,物件導向,就是「你現實生活會怎麼做,你程式就這樣寫。」在現實生活中,我們叫別人做事,有時候會期待對方簡單回覆執行狀況如何,有時候不會,讓對方有事再回報就好。在物件導向的程式設計中也是一樣,大部分的 Command,如果凡事正常,對方要嘛不回傳任何訊息,要嘛只是簡單回個 boolean 代表正確與否、或回個 int 代表影響資料筆數。最多,就是在發生錯誤時,丟個 Exception 出來。

無論如何,原則應該是要「簡單就好」,因為我就是不想管那麼多,才把事情委託另一個物件去辦嘛!都委託給別人了,我當然只要知道有沒有成功就好啦!我管那麼多細節做什麼?

Tell. Don't Ask

物件導向程式設計有這麼一句名言:"Tell. Don't Ask." 就是在講這樣的設計概念。

你說這跟單元測試有什麼關係?有關係。遵照上述原則設計時,函式的呼叫方幾乎不會預期得到什麼回信。如果我要測試的對象,會至少回傳某個值,在對方執行完畢後,我可以去驗那個值是否正確,但萬一對方什麼都沒回(void)呢?我要怎麼驗證他有做到他答應我的事?

這就是我們今天要聊的主題:測行為。

不回傳的 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 把申請表存起來。」寫完程式,該來考慮測試怎麼寫了。

Checker 的行為因為有時間的因素,不太好控制,所以可以用我們上一篇練習過的 Mock 工具,讓它按我們意願,回傳 true 或 false。回傳 true 時,因為 repository 沒有回傳值,所以我們要來驗證他「有沒有被呼叫 create 一次」。在 Java 使用 Mockito 的話,就可以搭配 verify 的 API,寫起來就像這樣:

@Test
void check_ok_then_create() {

    // 準備申請表
    Application application = new Application(777L);

    // 準備假 checker
    ApplicationChecker checker = mock(ApplicationChecker.class);
    when(checker.checkTime(application)).thenReturn(true);

    // 準備假 repository
    ApplicationRepository repository = mock(ApplicationRepository.class);

    // 執行
    new ApplyScholarshipService(checker, repository).apply(application);

    // 驗證:真的有 create 一次
    verify(repository, times(1)).create(application);
}

這樣就測完了。此時我們證明了,一旦 check 的檢查通過了,我們的 service 真的會叫 repository 去 create 一次。接著,下一個測項應該就是 checker 檢查不通過而回傳 false 的情形了吧?在此情況下,測試應該要大同小異,只差在最後要 verify 的是「create 從未被呼叫」。

礙於篇幅,我就不貼上來了。 github 上有完整測項,讀者可以下載來看看。

Reference

  1. Tell. Don't ask:https://martinfowler.com/bliki/TellDontAsk.html
  2. GitHub Repository:https://github.com/bearhsu2/ithelp2021.git
tags: ithelp2021

上一篇
Day 06 「不聽話就換掉」測試與依賴:測資料 之 用 Mock 工具控制依賴
下一篇
Day 08 「說好的射後不理呢?」多線程環境下的單元測試
系列文
你就是都不寫測試才會沒時間:Kuma 的 30 天 Unit Test 手把手教學,從理論到實戰 (Java 篇)30

尚未有邦友留言

立即登入留言