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。

2010年10月14日 星期四

寄測試信到本機資料夾

緣由

開發程式時,我們都會建立Builde Server,每日建置並測試我們的程式是否正確。而寄信的功能是常見的需求,而每日測試時發信的問題就來了。

  1. Mail Server 難道一定要存在嗎?
  2. Mail Server 未能連線時,測試寄信的功能就會失敗。測試失敗看來就像是程式的問題。
  3. 這當然不是程式的問題,因為程式一直都沒動啊!
  4. 結論:測試無效==>測試無用。

這是老話題了。因為這已經不是單元測試了,而是整合測試。

那,我該如何進行寄測試信的「單元測試」呢?

解法

最簡單的測試方法,是修改 config,讓寄信的功能,改寄到本機資料夾,就不會亂發信。測試信真的寄給了客戶,客戶還會以為是真的。測試信寄太多,也會被當成垃圾信的跳版而被封鎖呢。

修改 config 如下。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.net>
        <mailSettings>
            <smtp from="mymail&lt;testmail@domain.com.tw&gt;" deliveryMethod="SpecifiedPickupDirectory">
                <specifiedPickupDirectory pickupDirectoryLocation="c:\temp\mails" />
            </smtp>
        </mailSettings>
    </system.net>
</configuration>

記得要先建立 pickupDirectoryLocation 所指定的目錄,否則還是會發生例外。

PS: 這樣還是會測到「檔案系統」,嚴格來說並非完全是道地的單元測試。不過至少,不寄測試信的目的達到了。

參考

2010年10月12日 星期二

TFS 2010 自動部署 Web

今天一直在測試自動部署 Web Application 到 Windows Server 2008 的功能。

先部署最簡單的 case: 直接部署到 Build server 並進行測試。

然而,之前的方法不知為何一直行不通?難道是 Windows2008 的原故嗎?

找了好久,終於找到了原因。原來不知道我新的 Build Server (for Windows Server 2008)哪個步驟省略掉了,導致 %Program Files%\MSBuild\Microsoft\VisualStudio\v10.0\ 下少了 Web 這個目錄。從別台伺服器 copy 過來,就又可以自動部署了。

image

PS: 之前也 copy 過 WebApplications 這個目錄,才能 Daily Build。真的忘記哪個步驟被忽略了。

2010年10月7日 星期四

No Code Coverage Results

問題

在 TFS 伺服器上,老是出現「No Code Coverage Results」的訊息。我就是要知道程式碼涵蓋率啊!

1 test run(s) completed - 100% average pass rate (100% total pass rate)
No Code Coverage Results

設定

目前方案的測試設定,是使用哪一個設定呢?我都是使用 Local (local.testsettings)這一個。

image

在Solution Items中,可以找到這一個項目。雙擊該項目,打開如下圖的設定。記得將 Code Coverage 勾選。

SNAGHTML44ee3d

按「Configure」,或雙擊「Code Coverage」,必須再設定對哪些  assembly 進行code coverage 的分析。

SNAGHTML47521e

這樣的設定下就成功了。先在本機跑一下,確定可以跑出 code coverage後,再 check in 到 TFS 上吧。

SNAGHTML4aa744

原因

在vs2010中,又多了一個TraceAndTestImpact.testsettings的設定。開發時,如果要了目前程式的修改衝擊到哪些測試,就必須使用這個一測試設定。而一旦設定了這個測試設定,很容易就忘記了,就這樣 check in 到 TFS中。這就是原因所在。?

正在設定windows更新 0% 完成 請勿關閉電腦

過程

同事今早抱著 NoteBook 來問我,已經一個星期了,開機和關機都離譜的慢。開機時,會出現「正在設定windows更新 0% 完成 請勿關閉電腦」,關機時,會顯示「正在安裝更新1/2」的訊息。但是,每次開關機都會出現。

天啊,對於只寫程式的我,這是一大難題。查了 Google,有一堆說要系統還原,有一些說要手動安裝更新。但,這都要花不少時間。

我直接採用的最簡單的一種方法,出奇地非常有效,一次就解決了。

方法

開機後,等到Windows 更新的訊息結束,且在敲入帳號密碼前,此時畫面右下方會有一個紅色的按鍵。選擇「關機」,然後系統就會關機了。

再開啟電源,惱人的更新訊息就消失了。

討論

這個真的很怪,正常的解決方法不應該是這樣的。如果您被這個問題困擾很久,不如使用這個方法試試看。

2010年10月5日 星期二

ASP.NET 錯誤:找到模稜兩可的符合項目

同事問我這個問題怎麼解?

剖析器錯誤
描述: 當剖析服務此要求所需的資源時發生錯誤。請檢閱下列的特定剖析錯誤詳細資訊,並且適當地修改您的原始程式檔。

剖析器錯誤訊息: 找到模稜兩可的符合項目。

SNAGHTML1c362fe

過程

這個問題花去我不少的時間。第一時間還以為是多餘的 dll 殘存在 bin 目錄下。找了很久沒有找到。

並且,這是 WebSite 的模型,經由 publish 得到的 dll,並不會有上述殘存的 dll。

終於,使用 .net reflector 找到了問題所在。

原因

這原本是個 VS2003 開發的程式,而 VS2003 只有一種 Web application 的模型。經由不知名的人改成 Web site 模型,又不小心將 aspx 中,原來 ID=”_hid002oid” 的 control,改名為 ID=”_hid002OID”,雖然只是大小寫的差別,但在 C# 就足以形成兩個 member field了。

image

解法

將aspx 與 codebehind  的 control 名稱統一即可。

Share with Facebook