2009年8月28日 星期五

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

繼上一篇的單元測試(1): 什麼是單元測試

單元測試的寫的好不好,影響執行測試結果的可信任度。

若測試莫名其妙就失敗,難以找到原因,或者錯誤卻不是由自己的受測試程式所造成,而是依賴的其他系統/服務的停止或失敗,測試的可信任度會愈來愈差,沒過多久,就沒有人相信測試的結果。到了最後,就連測試也不執行了。

所以,單元測試要寫好,就是要不相依於其他的服務/系統/程式。

以下舉個例子。

受測的程式
 
using System.IO;

namespace ClassLibrary1
{
  public class Class1
  {
    /// 
    /// 讀取檔案行數
    /// 
    public int CountFileLineNumber(string filePath)
    {
      return File.ReadAllLines(filePath).Length;
    }
  }
}
BAD單元測試
using ClassLibrary1;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace TestProject1
{
  [TestClass()]
  public class Class1Test
  {
    [TestMethod()]
    public void CountFileLineNumberTest()
    {
      Class1 target = new Class1();
      int actual =target.CountFileLineNumber(@"d:\temp\aaa.txt");
      Assert.AreEqual(4, actual);
    }
  }
}
以上的例子是可以正常測試的。那到底什麼地方不好呢?
首先,該測試通常受到版本控管。當別人在他的電腦取得測試後,一旦執行該測試後,通常會失敗。原因:找不到 d:\temp\aaa.txt。
因為該測試必須有 aaa.txt 的輔助才能進行,如果沒有將該檔案一起放到 source control,其他同事進行測試時就會失敗。

再者,別人的電腦, d drive可能是光碟,也可能沒有對應的邏輯磁碟。
這就是「相依於檔案系統」造成的問題。

該怎麼改呢?首先將aaa.txt 加到測試專案中,並且將Copy to Output Directory 的值設為 Copy always。這樣一來,該測試檔案就會跟著專案一起了。

image

再來,將測試程式修改成下方。

using ClassLibrary1;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace TestProject1
{
  [TestClass()]
  public class Class1Test
  {
    [TestMethod()]
    [DeploymentItem("aaa.txt")] <!-- 多這一行 -->
    public void CountFileLineNumberTest()
    {
      Class1 target = new Class1();
      int actual =target.CountFileLineNumber(@"aaa.txt"); <!-- 修改這一行 -->
      Assert.AreEqual(4, actual);
    }
  }
}

DeploymentItem 指名要將 aaa.txt 放到測試目錄一同進行測試,而程式碼在讀取的也會在執行目錄上找檔案。

這樣一來,該「相依於檔案系統」的依賴性就消除了,也就是就更像是「單元測試」了。

Another Mock Framework: Moq

在看單元測試相關的文章後,發現也不少人使用 Moq.

Study 了一下,Ouch! 也相當棒呢!

重點是:非常活躍!看一下release 吧!每個月至少都有三版本 release 呢!

Windows 7 的快速鍵

Windows 7 用了一陣子,實為好物。

而快速鍵呢?記不了這了多,就請到

http://windows.microsoft.com/en-US/Windows7/Keyboard-shortcuts 這裡查囉。

2009年8月25日 星期二

軟體專案的成功率

根據 CHAOS Summary 2009 的報告 (2009 Aug),軟體專案是否成功的比例如下

 

Percentage

succeeding

32%

challenged

44%

failed

24%

 

succeeding: 如期,如質,如預算

challenged:雖然完成了,但時程不如預期,或超支、或刪減需求、或品質不佳等

failed:取消專案,或雖然做完了,卻從不使用。

記得多年前,failed 是 25%, 而 chanllenged 是 51% 吧!總體成功率是上升了,但仍然沒有太大的變化。

2009年8月20日 星期四

單元測試(1): 什麼是單元測試

什麼是單元測試呢?在 Visual Studio 2008 上,在 method 上很簡單,按個右鍵,就可以建立一個單元測試了。如下圖。
image

但是,接下來寫的,就一定是「單元」測試嗎?

類似的問題也發生在物件導向語上。「使用c# 語言開發的程式,一定符合物件導向的程式碼嗎?」

使用c#來寫程式,也需要有物件導向的精神,才能寫出物件導向的程式。這是人為的。
使用Create Unit Tests 的工具來建立單元測試,也必須讓我們寫的測試符合「單元」的精神,才是真的單元測試。

定義

  1. A unit test is a piece of a code (usually a method) that invokes another piece of code and checks the correctness of some assumptions afterward.
  2. If the assumptions turn out to be wrong, the unit test has failed.
  3. A “unit” is a method or function

注意到第3點,unit 所指的測試範圍是一個 method 及 function 的程式碼。

舉例來說,以下的程式碼可以做單元測試,因為沒有涉及其他的程式碼

 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ClassLibrary1
{
  public class MyMath
  {
    public int Add(int a, int b)
    {
      return b + a;
    }
  }
}

但下面的程式碼就不容易做單元測試。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Configuration;
using Microsoft.Practices.Unity;

namespace ClassLibrary1
{
  public class Project
  {
    private ISettings _settings;
    IAuthenticationService _authenticationService;
    public Project(ISettings settings, IAuthenticationService authenticationService)
    {
      this._settings = settings;
      this._authenticationService = authenticationService;
    }

    [Dependency]
    public IWriteLog Logger { get; set; }

    public float GetProjectActualHours(int vProjId, string userName, string pwd)
    {
      if (!_authenticationService.Authenticate(userName, pwd))
      {
        Logger.WriteLog("xxx ");
        throw new NotFiniteNumberException();
      }

      string xmlPath = _settings.XmlPath;
      XElement el = XElement.Load(xmlPath);

      var projectNode = (from e in el.Elements("project")
                         where (int)e.Attribute("ID") == vProjId
                         select e).FirstOrDefault();

      if (projectNode != null)
      {
        var q = (from e in projectNode.Elements("rec")
                 select (float)e.Attribute("Hours")).Sum();
        return q;
      }
      else
      {
        string message = "hello, no projId = " + vProjId;
        return 0;
      }
    }
  }
}

原因呢?當我們在進行GetProjectActualHours 的單元測試時,如果所呼叫的_authenticationService.Authenticate 方法失敗時,應該屬於GetProjectActualHours方法的測試失敗,還是_authenticationService.Authenticate的失敗?

因此,當測試的對象引用到別的程式時,就不容易進行單元測試了。

但是,無論如何,還是可以進行測試。只是,此時的測試不再是「單元」測試了,而是「整合」測試,整合其他的服務/元件進行「整合測試」。或者,「元件測試」的名詞較容易為大家接受

請繼續讀 單元測試(2): 單元測試的好與壞

2009年8月13日 星期四

Windows 7 (x64) 上執行 Visual Studio 2008,執行單元測試時,發生 Code coverage collection error

歡天喜地地安裝完 Windows 7 64 位元版後,就跟著安裝一堆開發用的軟體。
還真的不少。已過了三天,還裝不完呢。

在 Visual Studio 2008 上執行之前的單元測試,全掛了!錯誤原因如下:

Code coverage collection error: The Visual Studio performance and coverage logging engine is already running on the computer. Therefore, the test run cannot continue. Close the pending performance or code coverage session and then rerun the tests.

是什麼意思呢?原來不知為何,Visual Studio 的效能程式 VSPerfMon.exe 無法自行關閉,因此也無法再起一個。

碰到這種情形時,請手動到 task manager 上強制 end process 即可。

2009年8月10日 星期一

Win 7 的安裝

由於之前使用的是 Vista 32bit,這一次想要安裝的是 64 bit. 因此無法直接升級,只好重新安裝了。

安裝的經驗:快!快到我實在驚訝。在家中,忘了計算時間。

今天在公司安裝時,測試一下,只需要 18 分鐘就安裝完成了。

開機、關機、也都比以往快上許多。

2009年8月7日 星期五

Windows 7 終於出來了

最終的版本,終可以可在 MSDN 訂閱下載了。

在颱風夜…

2009年8月3日 星期一

asp.net 的帳號

asp.net 的帳號,有好多個。

其中,最常用的就是 Page.User.Identity,代表使用者登入後,系統認定該使用者的帳號。

這有什麼問題呢?原來,帳號的學問可大著呢。

 protected void Page_Load(object sender, EventArgs e)
    {
      lblPage.Text = this.Page.User.Identity.Name;
      lblThread.Text = Thread.CurrentPrincipal.Identity.Name;
      lblWindowsIdentity.Text = WindowsIdentity.GetCurrent().Name;
      lblLogonUserIdentity.Text = this.Page.Request.LogonUserIdentity.Name;
    }
Identity 說明
this.Page.User.Identity 使用者登入後,系統用來識別該使用者的帳號
Thread.CurrentPrincipal.Identity 這個我不太了解。通常與this.Page.User.Identity相同。
WindowsIdentity.GetCurrent() 代表執行該網頁時,Windows 真正執行的identity
this.Page.Request.LogonUserIdentity IIS 上設定的執行帳號。IIS 6 以後,就是 Application Pool 所指定的identity,通常是 Network Service

這有哪些變化呢?

  1. IIS 上的驗證方式。有 anonymous, windows NTLM。
  2. IIS 上 Application pool 的 identity
  3. web.config 上的 <authenticaion mode=”Forms” or mode=”Window”
  4. web.config 上 <identity impersonate="true", 或 <identity impersonate="true" userName="userName" password="password"/>

這些設定,都可能影響上述4個 identity 的值。由於變化太多了,我也無法完全記錄下來。大家就自行試試看囉!

範例程式下載

2009年8月2日 星期日

patterns & practices Application Architecture Guide 2.0,成為架構師必讀

微軟不斷地提升 .net 的能量,也推出了許多的公開文件。
其中,要讓我們的系統能長長久久地運作下去,好的架構自然是必要選項。

但,系統類型何其多,如 WebForm, WinForm, 甚至 SilverLight,如何作架構設計呢?

patterns & practices Application Architecture Guide 2.0 是您必讀的對象。

Share with Facebook