1. 替 App_Code 目錄下的 code 寫單元測試 ?

    有在寫 .net / java 的人, 大概都聽過這套很有名的 framework ... JUnit / NUnit. NUnit 是從 JUnit 那套移過來的, 用法觀念大同小異, 不過 porting 到 .net 卻是充份的應用到了 .net 提供的優點...

    Microsoft 在 ASP.NET 2.0 提供了一個很有用的機制, 就是在你的 web application 下, ~/App_Code 目錄下的所有 code 都會被視為 source code, 在 ASP.NET Hosting 環境下會自動的編譯及執行. 意思就是 source code 丟進去這目錄就 ok 了, 不需要手動執行 compile 的動作.

    這便民的機制卻引來不少困擾, 最大的問題就是某些一定要找的到 assembly (.dll) 檔案的情況下, 少掉 dll 就變成是件麻煩事. 我自己碰到的情況就是這樣, 我想要依賴 App_Code 帶來的好處, 但是我又希望能替 App_Code 下的程式碼寫單元測試案例, 問題來了:

    1. App_Code 沒有對應的實體 *.dll
      雖然在 c:\Windows\Microsoft.NET\Framework\Temporary ASP.NET Files\ 下找的到, 但是目錄編碼過, 每次都有可能不一樣, 就算找的到也很不方便
    2. App_Code 下的程式大都仰賴 ASP.NET Hosting 提供的環境才能執行
      比如 HttpContext ... NUnit 提供的 Test Runner 會另外用獨立的 AppDomain 下執行, 結果是跟 Hosting 環境相關的 code 就沒機會測試了

    試了半天, 宣告放棄, 實在是找不到好方法完成這任務... Microsoft 自己的 Unit Test Framework 出局, NUnit 也出局, 我甚至去挖了 NUnit 的 source code 來看, 看的臉都綠了...

    NUnit Test Runner 做的非常嚴謹, core 裡就把每個 test 執行的環境準備好了獨立的 AppDomain, 從指定的 assembly 載入 class 後就會丟到獨立的 AppDomain 下開始跑 test ... 本來想改掉, trace 了一陣子後就放棄了, 我只是要跑單元測試啊, 我不想要有我自己改過的 NUnit Framework 啊啊啊啊...

    後來 google 找了一堆相關網站, 總算找到救星了... NUnitLite. 看了它的 vision, 不錯, 正好是我需要的...

     

    The NUnitLite Vision

    NUnitLite is a lightweight test framework for .Net, implementing a limited subset of NUnit's features. It will use minimal resources and be suitable for execution on various classes of devices.
    NUnit itself is fairly full-featured and becomes more so with each release. As a result, it is difficult to use in certain projects, such as embedded applications and testing add-ins for other software products.
    NUnitLite is delivered as source code only, for inclusion directly in the users' test projects. It will not suffer the weight of NUnit features like extensibility, a gui, multi-threading, use of separate AppDomains, etc. All these features have their place but they can generally be dispensed with in resource-limited situations or when the tests must run in some special environment.
    The NUnitLite codebase is completely separate from NUnit, although obviously inspired by it. Features added to one pof the two products will not automatically be incorporated in the other.
    NUnitLite will initially support Microsoft .Net, Mono and the .Net compact framework.

     

    我標紅字的地方, 就是我最需要 NUnitLite 的原因. ASP.NET web sites 正好運作方式就像 IIS 的 add-ins 一樣, 本來就很難獨立的運作, 本來就很難把要測試的部份抽出來. 就算硬抽出來, 能測的程式碼函蓋率一定也不高. 想想看, 你的 ASP.NET code, 都不用到 HttpContext (Application, Session, Request, Response... ) 的部份, 佔了多少比重? 剩下的那部份完全不會出錯嘛?

    總之現在已經找到滿意的 solution 了 [:D], 雖然 NUnitLite 只有 0.1.0.0 而以, 而且最新的 release 還不能 build...  =.= 不過至少也能很有效的解決我問題了 [:D]

    實際的用法, 下次再改貼另一篇...

    2006/10/29 .NET 技術隨筆

  2. 利用 NUnitLite, 在 App_Code 下寫單元測試

    繼上篇寫了些 543, 說 NUnitLite 可以用來測 Asp.Net 2.0 Web Application 後, 這次來個簡單的範例. 因為我公司主要開發的是 web based application, 因此常常碰到寫好的程式可能會被安裝到好幾個不同 site 的情況, 甚至安裝的人員也不見得是懂 coding 的人.. 因此確認 configuration 是否正確就是很重要的一環. 這個例子就來把 "configuration 是否正確" 這件事, 也當做 unit test 的一部份.

    也許有人會講, 這是環境的測試, 而 unit test 主要是要測程式的最小單元 - function 是否正常, 話是沒錯, config 之類的問題應該用 trace / assert 也許較恰當, 或是整個 system initialization 時就應該自我檢查一番. 這樣沒錯, 不過我的看法較實際一點, 除非你的開發人員很多, 或是你的產品量已經大到值得你這麼做, 否則大部份的專案, 我想這部份都是被呼略掉的一環...

    因此, 既然 NUnit Framework 已經讓測試這麼方便了, 為什麼不順便用這種機制來做些額外的檢查? 好, 不多說, 來看 sample project:  NUnitLiteWebSite

    首先, 你寫好的 test fixture, 總要有 test runner 來測試它. Nunit 有 gui / console mode runner, 但是 NUnitLite 沒有, 而且 App_Code 最簡單能執行的方式也只有 asp.net page (其實我實際應用的案例, 連 ASP.NET Hosting 都用上了, 不過這就有點偏離主題了). 所以...

    Step 1. 先建立一個 Web Site Project.. [download]

    Step 2. Reference NUnitLite.dll, 請自行到 NUnitLite 網站下載 source code, 自己 build 吧.

    Step 3. 實作 NUnitLiteTestRunner.aspx 這個網頁, 目標是 browser 開啟這網頁, 就能看到像 NUnitConsole 輸出的結果畫面... (當然醜一點沒關係... ) 主程式很短, 如下...

     

       13 public partial class NUnitLiteTestRunner : System.Web.UI.Page

       14 {

       15     protected void Page_Load(object sender, EventArgs e)

       16     {

       17         this.Response.ContentType = "text/plain";

       18         Console.SetOut(this.Response.Output);

       19 

       20         ConsoleUI.Main(new string[] {"App_Code"});

       21     }

       22 }

     

    應該沒啥需要說明吧?
    第一行指定輸出的 content type 是純文字...
    第二行把 Console.Output 導向到 Response.Output, 這樣 browser 才收的到輸出內容
    第三行, 呼叫 NUnitLite 內建的 console test runner ...
    第四行, 沒... 沒事做了

     

    Step 4. 接下來就簡單了, 直接在 App_Code 下把你要的 TestFixture 通通丟進去... 我準備了一個例子, 主要做兩項測試. 一個是確認 session 有啟用, 另一個就是確認 configuration 裡指定的 temp folder 是否存在, 是否真的能夠 create / read / write / delete temp file.

       16 [TestFixture]

       17 public class ConfigurationTest

       18 {

       19     [Test]

       20     public void SessionEnableTest()

       21     {

       22         //

       23         //  確認session 是啟用的

       24         //

       25         Assert.NotNull(HttpContext.Current.Session);

       26     }

       27 

       28     [Test]

       29     public void TempFolderAccessTest()

       30     {

       31         //

       32         //  確認設定檔指定的temp folder 存在

       33         //

       34         Assert.True(Directory.Exists(ConfigurationManager.AppSettings["temp-folder"]));

       35 

       36         string filepath = Path.Combine(ConfigurationManager.AppSettings["temp-folder"], "test.txt");

       37         string content = "12345";

       38 

       39         //

       40         //  確認可以寫暫存檔

       41         //

       42         File.WriteAllText(

       43             filepath,

       44             content);

       45 

       46         //

       47         //  確認可讀, 且內容跟寫入的一樣

       48         //

       49         Assert.AreEqual(

       50             File.ReadAllText(filepath),

       51             content);

       52 

       53         //

       54         //  確認可刪除, 不會有exception

       55         //

       56         File.Delete(filepath);

       57     }

       58 }

     

    Step 5. Deploy Web, 調整好各項 configuration, 啟用前先點一下 NUnitLiteTestRunner.aspx, 看看結果如何... 但是我的 IE 很怪, 明明 Step 3 的 code 都已經把 content type 都標示為 text/plain 了, 我的 IE 硬要把它當 xml 來開, 然後才唉唉叫說 xml 有問題.... 結果就變這樣:

     

    如果你剛好也碰到, 就直接按右鍵選 view source 就好...


    NUnitLite version 0.1.0
    Copyright 2006, Charlie Poole
    
    Runtime Environment -
        OS Version: Microsoft Windows NT 5.1.2600 Service Pack 2
      .NET Version: 2.0.50727.42
    
    2 Tests : 0 Errors, 0 Failures, 0 Not Run
    

    故意把 web.config 裡的 session 關掉看看...


    NUnitLite version 0.1.0
    Copyright 2006, Charlie Poole
    
    Runtime Environment -
        OS Version: Microsoft Windows NT 5.1.2600 Service Pack 2
      .NET Version: 2.0.50727.42
    
    2 Tests : 0 Errors, 1 Failures, 0 Not Run
    
    Errors and Failures:
    
    1) SessionEnableTest (ConfigurationTest.SessionEnableTest)
      Expected: not null
      But was:  null
    
       於 ConfigurationTest.SessionEnableTest()
    
    

    哇哈哈, 真的如我預期的可以運作, 真是太好了. 同樣的例子大家可以換 NUnit 來試試, 一樣可以 run, 不過一來 NUnit 的 assembly 太多, 二來它會無法載入 App_Code 這個 assembly, 三來它會很嚴僅的另外建 AppDomain, 另外用獨立的 thread 來跑你的 test case, 這些動作都是非常不建議用在 web application 下的, 除非你很確定它對系統的影響... 所以, NUnit 還是留給更大規模更嚴僅的開發專案吧, 較簡單快速的 web application, NUnitLite 還是很好用的 [Y]

    看完了, 對你有幫助的話, 來點掌聲吧 [H]

    2006/10/29 .NET

  3. ASP.NET Tips: Launch ASP.NET Web Sites without IIS / VS2005

    恩, 題目定的很偉大的樣子.. 其實只是個不起眼的小技巧而以.. 以往開發網站程式都要裝 IIS, 到 visual studio 2005 後就有內建的 Develop Web Server 可以用.

    不過還是很麻煩, 比方說我另外一篇文章講到 NUnitLite 在 Web Application 上的應用, 我有提供 sample code, 抓下來後, 你可能會把它放到 iis 做些設定跑看看, 或是直接用 visual studio 2005 開 web site 後按 f5 跑看看... 兩種方式看來都很麻煩, 尤其我用 notebook, 開個 visual studio 2005 要等半天, 不是很有吸引力的 code 我可能就懶的開了, 哈哈..

    因為懶, 所以才有這 tips .. 我自己寫了個簡單的 batch file, 然後把它的捷徑放到 c:\Documents and Settings\{your account name}\SendTo 下, 就大功告成了.

    以下是批次檔的內容:

     


    set DEVWEB_PORT=%random%
    start /min /low c:\Windows\Microsoft.NET\Framework\v2.0.50727\WebDev.WebServer.EXE /path:%1 /port:%DEVWEB_PORT%
    start http://localhost:%DEVWEB_PORT%/
    @set DEVWEB_PORT=


     

     

    用的時後怎麼用? 以我前面舉的例子來說:

    1. 把下載的檔案解開
    2. 在檔案總管裡找到這個目錄
    3. 目錄上按右鍵, 選 "傳送到" --> "Dev ASP.NET Web" (就是剛才拉的捷逕)

    好, 大功告成... 這個動做就會像 visual studio 2005 一樣, 幫你把 dev web server 開起來, 同時幫你把 browser 也開起來..

    enjoy it :D

    2006/10/28 .NET

  4. 泛型 + Singleton Patterns (II)

    上篇因為貼 code , 放一起實在太長了, 只好分兩篇... 吊完胃口, 不囉唆了, 直接看我想出來的解法. 原則還是跟一般的函式庫一樣, 我希望先做出一個 base class, 把 singleton 的實作細節都處理掉, 函式庫的目的是讓使用你 lib 的人會很快樂才對, 因此 base class 可以辛苦點沒關係, 但是絕不能讓用你 code 的人得做苦工...

    好了, 我實做出來的版本, code 如下:

        7     public class GenericSingletonBase<T>

        8         where T: GenericSingletonBase<T>,

        9         new()

       10     {

       11         public readonly static T Instance = new T();

       12     }

    沒看錯, 就是只有這幾行... 接下來貼的 code 是, 如果我自己要實作 singleton pattern 的 class 時, 該如何來用這個 lib:

       14     public class GenericSingletonImpl1

       15         : GenericSingletonBase<GenericSingletonImpl1>

       16     {

       17         public GenericSingletonImpl1()

       18             : base()

       19         {

       20             Console.WriteLine("GenericSingletonImpl1.ctor()");

       21         }

       22     }

    扣掉非必要的 constructor, 其實 class 繼承的部份寫完, 就沒有其它必要的 code 了, 很好, 又滿足了我一個要求...

    再來就剩最後一個, 要用這個 class 的 code 會不會像上一篇的例子一樣醜? 每次都要自己 casting ? 再看一下 code ...

       21             GenericSingletonImpl1 o1 = GenericSingletonImpl1.Instance;

       22             GenericSingletonImpl1 o2 = GenericSingletonImpl1.Instance;

       23             GenericSingletonImpl1 o3 = GenericSingletonImpl1.Instance;

    很好, 收工... 哈哈... 謝謝大家的收看 [:D]

    2006/10/27 系列文章: 泛型 + Singleton Patterns .NET

  5. 泛型 + Singleton Patterns

    聽起來 singleton 跟 generic 好像搭不上邊, 不過搭配 .net framework 2.0 的 generic 機制, 倒是可以讓 singleton 好做很多... 我先簡單寫一下不使用 generic 時的做法...

    只有單一 class 要實作 singleton 很簡單, 只要寫這樣的 code 就可以:

        7     public class SampleSingletonClass

        8     {

        9         private static SampleSingletonClass _instance = null;

       10         public static SampleSingletonClass Instance

       11         {

       12             get

       13             {

       14                 if (_instance == null)

       15                 {

       16                     _instance = new SampleSingletonClass();

       17                 }

       18 

       19                 return _instance;

       20             }

       21         }

       22 

       23         private SampleSingletonClass()

       24         {

       25             //

       26             //  ToDo: constructor code here.

       27             //

       28         }

       29     }

     很標準的 code, 不是嗎? 不過問題來了... 當我有第二個 class 也要套用 singleton patterns 時, 幾乎一樣的 code 就得再抄一次, 只因為 public static XXX Instance; 這個 static property 的型別不一樣, 很討厭...

    這堆看起來差不多的 code 想要省掉, 那只好動點手腳, 用繼承的技術解決, 不過問題又來了, 型別的宣告... 就像一堆 Collection 物件一樣, 傳回型別宣告為 object 就好了, 不過這樣的 code 用起來實在麻煩... 寫起來就像這樣:

        8     public class SingletonBase

        9     {

       10         public static SingletonBase Instance(Type seed)

       11         {

       12             if (_singleton_storage[seed] == null)

       13             {

       14                 _singleton_storage[seed] = Activator.CreateInstance(seed);

       15             }

       16 

       17             return _singleton_storage[seed] as SingletonBase;

       18         }

       19 

       20         private static Hashtable _singleton_storage = new Hashtable();

       21     }

       22 

       23     public class SingletonBaseImpl1 : SingletonBase

       24     {

       25         public SingletonBaseImpl1()

       26             : base()

       27         {

       28             Console.WriteLine("SingletonBaseImpl1.ctor() called.");

       29         }

       30     }

       31 

       32     public class SingletonBaseImpl2 : SingletonBase

       33     {

       34         public SingletonBaseImpl2()

       35             : base()

       36         {

       37             Console.WriteLine("SingletonBaseImpl2.ctor() called.");

       38         }

       39     }

    看來不怎麼漂亮? 不過看在重複的 code 只寫一次就好的份上, 醜一點關起門來就看不到了. 不過這樣就沒事了? 不... 用起來更醜... [:'(]

       11             SingletonBase.Instance(typeof(SingletonBaseImpl1));

       12             SingletonBase.Instance(typeof(SingletonBaseImpl1));

       13             SingletonBase.Instance(typeof(SingletonBaseImpl1));

       14 

       15             SingletonBase.Instance(typeof(SingletonBaseImpl2));

       16             SingletonBase.Instance(typeof(SingletonBaseImpl2));

       17             SingletonBase.Instance(typeof(SingletonBaseImpl2));

     

    實在無法接受這種 quality 的 "class library" ... 這種 code 看起來一點美感都沒有, 就像文筆不好的人在寫文章一樣...

    處女座的個性, 實在不能容忍這種 code 出現在我的 project 裡... 碰到這種問題, 直覺的解決辦法就是:

    1. 透過 inherence, 把這些重複的 code 集中到 super class 一次解決
    2. 同樣邏輯, 要套用到不同型別的應用, 就用 generic 的方式處理

    不過要實作還沒那麼簡單, 試了半天, 總算找出一種看起來最得意的解法... <待續>

    2006/10/26 系列文章: 泛型 + Singleton Patterns .NET