2010年10月19日 星期二

單元測試(7): 使用 Moles 增加可測試性

在多次的文章中討論到可測試性。為了增加 method 的可測試性,往往需要更改 method 的參數,讓不具可測試性的 method,抽離出可測試的部份。

問題

靜態函式往往是最不具測試性的原兇。例如 DateTime.Now, File.Exists 等。為了這些障礙,我們必須一次又一次的拆解我們原本良好的 method 嗎?

舉例

public class MonthHelper
{
    /// <summary>
    /// 今天是否為單數月
    /// </summary>
    /// <returns></returns>
    public bool IsOddNumberMonth()
    {
        return DateTime.Today.Month%2 == 1;
    }
}

DateTime.Today 會隨著電腦時間而變,IsOddNumberMonth 也會因執行時間而回傳不同的結果,因此該 method 不具可測試性。為了可測試性,之前教大家要將此程式拆成兩部份。如下

public class MonthHelper
{
    /// <summary>
    /// 今天是否為單數月
    /// </summary>
    /// <returns></returns>
    public bool IsOddNumberMonth()
    {
        return IsOddNumberMonth(DateTime.Today);
    }

    /// <summary>
    /// 是否為單數月
    /// </summary>
    /// <returns></returns>
    public bool IsOddNumberMonth(DateTime dateTime)
    {
        return dateTime.Month % 2 == 1;
    }
}

這樣,我們就可以對 IsOddNumberMonth(DateTime) 進行測試。而原本的IsOddNumberMonth() 就只好放棄了。

問題在於,如此簡單的 method,卻必須為了可測試性拆成兩個 method,不太合理。萬一有個 method 裡有一堆不可測試的 method,如 DateTime.Now, File.Exists, File.ReadAllLines 等,我們需要拆解多少次才具可測試性?這樣的程式具可讀性嗎?

Moles

Moq 是非常棒的 Isolation framework,但只能對 interface 或 virtual method 進行 mock,對於靜態函式仍莫可奈何。

對此問題,微軟的 Microsoft Research 提出Moles 這一個 Isolation framework。其原理在於對整個 .NET Framework 的 mock,而非單一類別/方法的 mock。

以上例來說,我們可以進行下來步驟進行單元測試撰寫。(記得先下載及安裝 Moles)

1. 建立測試專案

在該 method 上按右鍵,執行「Create Unit Tests」

image

2. 加入 Moles 項目

在剛剛建立的測試專案上增加一個項目,選到 「Moles and Stubs for Testing」。此時項目的名稱必須與要模擬的 assembly 名稱一致。由於 DateTime 這個 type 的assembly 是 mscorlib 中,(見 DateTime 結構),所以名稱必須取作 mscorlib.moles。按 Add鍵到下一步驟。

SNAGHTML30c6ff

3. 建立測試

在測試程式的最上頭,增加 [assembly: MoledType(typeof(System.DateTime))]。這一行說明 Moles 的 runtime 要對 System.DateTime 進行模擬。
image

修改測試如下。[HostType(“Moles”)] 告訴測試環境是跑在 Moles 的環境下。而 MDateTime 則是對 DateTime 的模擬。Moles 對原來的 dotnet framework 的模擬,都使用固定的命名方式。MDateTime 就是對 DateTime 的 mock type. 屬性的 mock也區分 Get 與 Set。MDateTime.TodayGet 是對 DateTime.Today getter 的 mock。

image

4. 執行測試

以下是完整的測試內容。當然,測試結果都是成功的

using System.Moles;
using ConsoleApplication1;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using Microsoft.Moles.Framework;
[assembly: MoledType(typeof(System.DateTime))]

namespace TestProject1
{
    [TestClass()]
    public class MonthHelperTest
    {
        [TestMethod()]
        [HostType("Moles")]
        public void IsOddNumberMonth_Even_ReturnFalse()
        {
            MDateTime.TodayGet = () => new DateTime(2010, 12, 31);
            var target = new MonthHelper();
            bool actual = target.IsOddNumberMonth();
            Assert.AreEqual(false, actual);
        }

        [TestMethod()]
        [HostType("Moles")]
        public void IsOddNumberMonth_Odd_ReturnTrue()
        {
            MDateTime.TodayGet = () => new DateTime(2010, 11, 30);
            var target = new MonthHelper();
            bool actual = target.IsOddNumberMonth();
            Assert.AreEqual(true, actual);
        }
    }
}

結論

怎樣,相當棒吧。Moq 是我最常用的 isolation framework,執行起來會快很多。而當需要模擬 dotnet framework 的靜態方法時,我就會用 Moles。

2 則留言:

91 提到...

解決了我對.netframework原生函示,要做單元測試的疑惑了!感激不盡。

(我原本還想用typemock硬幹...)

劉士豪 提到...

你好,我在VS2012使用ShimGuid.NewGuid時出現ShimNotSupportedException,在網路找了一段時間也沒有明確的解法。

我是對System.dll做新增Fakes組件,在mscorlib.4.0.0.0.Fakes.messages
System.4.0.0.0.Fakes.messages
都產生類似以下的說明
"warning : 無法產生 System.DateTime 的 stub: 型別是實值型別。"

我一開始是可以使用,一周之後就產生以上問題。

請教一下,你是否也有遇到

Share with Facebook