應該為 Private Method 作單元測試嗎?

我在寫程式的時候常常遇到需要補測試的狀況,然而有的時候 class 太複雜、Private Method 也非常地多,常常會有「到底測試要寫到什麼地步才夠」的想法,此文章結合了我自己在做寫測試的心得以及許多開發者的想法,希望對讀者有一些啟發。

回到本題,到底該不該幫 Private Method 寫測試呢?有些人認為,當你的 Private Method 中只要含有邏輯,就應該替 Private Method 寫測試。而另一派人則認為,Private Method 之所以為 Private,就是因為這個函式應該被封裝在 Class 裡面,不應該暴露給外部呼叫,然而如果要對 Private Method 做單元測試的話,幾乎是一定得從外部呼叫,這也違反了 OOP 的封裝原則。

這兩種說法似乎都有道理,當程式的邏輯複雜到一定程度的時候,總會有一種「即使它是 Private Method,不幫它寫測試還是也有點不安」的程度。而後者則是以貫徹 OOP 設計模式的方向來思考。

某程度上,我的想法在兩者之間。

大部份時候,我認為不需要幫 Private Method 寫單元測試。理由很簡單,因為測試 Public Method 的時候就會連 Private Method 一起測試,如果 Private Method 有問題,十之八九會顯示在 Public Method 的測試上。

然而,有些時候這些 Private Method 的數量太多、或是它的 condition 實在太多,我們總是會很擔心 Public Method 沒辦法涵蓋所有可能性。當你有這個煩惱的時候,很有可能代表這個 class 已經做了太多事情,你必須要把這些 Private Method 拆出去變成另一個 class。

舉例來說,假設我們有個認證登入的 class,然後裡面有個功能,如果輸入密碼三次錯誤的話,帳號就會被鎖起來,接下來二十四小時無法再登入。

  def auth
    @retry_count = 0 unless account_locked?
    lock_account if @retry_count == 3
    @retry_count += 1 if auth_failed?
  end

  private 

  def account_locked
    @account.is_locked? && @account.locked_time < 1.day.ago
  end

  def lock_account
    @account.lock = true
  end

類似這種的時候,我們可以把帳號鎖定的功能單獨變成一個 Class,並且單獨對他做單元測試。如此一來,測試的過程會變得乾淨利落許多,也遵循了 single responsibility 的原則。

不過在某一種情況下,我會直接對 Private Method 做測試,那就是雖然函式的邏輯沒有很複雜,卻有許多條件式,也被廣泛利用在各個 Public Method 的函式當中。


  def method_a
    p_method(flag)
    # ...
  end

  def method_b
    p_method(flag)
    # ...
  end

  def method_c
    p_method(flag)
    # ...
  end

  private 

  def p_method(flag)
    case flag 
    when 1 then #.. do something
    when 2 then #.. do something
    when 3 then #.. do something
  end

一時之間完全想不到什麼好例子

按照上面那種情況的話,我們想要分別測試 method_a, method_b 以及 method_c ,我們必須同時測試三種不同的 Flag 的結果,但假設我們把 p_method 抓出來單獨做測試的話,在測試其他 Public Method 的時候只要做一個就行了,因為在 p_method 的測試中已經確保了它的正確性。

現實是殘酷的

有時候雖然你明白知道 Class 需要 Refactor,但礙於時程的關係你沒辦法當下著手,時間一久了也很難再去 Refactor,這就是所謂的 Technical debt,甚至有的時候我們只要看到接手的程式碼是有寫測試的就謝天謝地了,哪管得了那麼多?

不過,不管你認為 Private Method 值不值得一個 Test Suite,有測試確保程式的正確性總是好的。這件事情其實沒有標準答案,但希望這篇文章討論的一些想法能夠讓你在寫測試的時候多思考一點,寫出容易修改且簡潔的程式碼及測試。