2009年9月9日 星期三

單元測試(3): 單元測試的好與壞2

這個系列,請見
單元測試(1): 什麼是單元測試
單元測試(2): 單元測試的好與壞

另一種常見不好的單元測試,是測試與時間相關的程式。

舉例來說,有個 IsTimeMath() 的 method,要傳回當天的時間是否已過了當年的四分之三。程式大概如下:

 
  public class Class1
  {
    public bool IsMatch()
    {
      DateTime today = DateTime.Today;
      DateTime lastDayInYear = new DateTime(today.Year, 12, 31);
      return ((float)today.DayOfYear) / lastDayInYear.DayOfYear > 0.75f;
    }
  }
不好的測試程式如下
    /// 
    ///A test for IsMatch
    ///
    [TestMethod()]
    public void IsMatchTest()
    {
      Class1 target = new Class1();
      bool expected = false;
      bool actual = target.IsMatch();
      Assert.AreEqual(expected, actual);
    }

當在寫測試程式時,您會發現測試邏輯是想不出來的。原因呢?沒有任何參數需要輸入,卻需要聲明測試是否正確?很怪吧!
既然測試邏輯想不出來,代表原來的需求是錯的嗎?也不是,因為需求的確是「回傳當天的時間是否已過了當年的四分之三」。

那到底是怎麼樣?不是需求的問題,測試也寫不出來,到底該怎麼辦?
原來,追究原因,是 IsMatch 這個方法是難以測試的 (not testable),原因是與時間相關,而時間一直變動。
解決的方法,是讓 method 改成與時間無關,才是可測試的 method。改 IsMatch() 如下

public class Class1
  {
    public bool IsMatch()
    {
      return IsMatch(DateTime.Today);
    }

    public virtual bool IsMatch(DateTime today)
    {
      DateTime lastDayInYear = new DateTime(today.Year, 12, 31);
      return ((float)today.DayOfYear) / lastDayInYear.DayOfYear > 0.75f;
    }
  }

我做了什麼改變呢?我將程式改寫為兩個。第一個 IsMatch() 呼叫 第二個 IsMatch(DateTime today)。主要的邏輯放在第二個 IsMatch(DateTime today)
改變的動機,是
1. 第二個 IsMatch(DateTime today) 才具可測試性,才寫得出測試邏輯來
2. 第一個 IsMatch() 符合原需求。

這樣一來,IsMatch(DateTime currentDate) 才是可測試的,Unit test 才寫的出,而且也符合需求。

結論

我們所撰寫的程式,必須是符合「可測試性」,才有辦法寫出好的單元測試。

沒有留言:

Share with Facebook