2009/10/11

在 Visual Studio 中建立及進行單元測試

單元測試 (Unit Test) 是一個很基本的工作, 如果你使用 Visual Studio 做為開發工具的話, 建立單元測試專案是一件再簡單不過的事了。不過, 除非你已經對單元測試很熟悉, 否則有些最基本的概念, 你最好能事先熟悉一下

不過, 話說在前面, 如果你不懂、或不喜歡多層次架構的話, 你其實可以不必往下面看了。為什麼? 因為單元測試最適合用來測試以類別為大單位、以方法為小單位的自動化測試。如果你是習慣在一個網頁中把所有邏輯整個做完的話, 你或許最後會發現單元測試對你的幫助真的不大, 甚至可以說是多此一舉。那麼何必浪費時間呢?

其次, 你最好有個心理準備, 那就是撰寫單元測試的時間, 恐怕要比你寫原始程式的時間還要多個幾倍。我在最近一個專案中, 把「測試先行」這個目標很嚴格的落實了, 結果我發現光是撰寫單元測試, 所花費的時間跟人力就花掉我大部份的時間。為什麼? 因為寫一個邏輯出來很簡單, 但是要使用各式各樣的方法來驗證程式的可靠性, 那可不簡單 (何況我還是 QA 出身的)。有時候, 你恐怕還需要為單元測試做單元測試, 免得測試程式本身出了問題。

所以說, 雖然「測試先行」是 Agile development 的基礎概念, 但是如果你的專案是極度的急速變動的, 或者說你的老板是早上想到什麼, 下午就要的, 我想你大概也不可能有那個悠哉的時間寫測試程式。在這種狀況下, 單元測試不但對你沒有幫助, 反而會變成負擔。所以你如果決定要在專案中加入單元測試, 以上的幾個顧慮都必須事先考量到。

撰寫單元測試雖然辛苦, 但這些辛苦最後都會有回報。第一, 你其實在撰寫測試程式的時候就已經開始在做原始碼的測試了。就算你先寫測試程式再寫原始程式, 你還是能在很早的階段就發現邏輯上的問題, 等到真正寫程式碼的時候, 你的思路一定更為正確且穩定。第二, 當你把單元測試寫好了, 未來如果程式有任何修改, 你只需要讓單元測試自己跑過一遍, 等於是讓它把所有原先設計好的測試都自動做過, 幾乎可以說就像我們 QA 術語中的 "full pass"。思考一下, 如果你要以人工做過一次 full pass, 那將有多麼費工夫! 如果交給機器去跑, QA 就輕鬆了!

當然, 這不表示機器測試就可以取代人工測試。因為機器測試擅長的在於迴路、叉徑、極限值、大量、完整度等程式邏輯的測試, 卻無法做出某些以人類角度來看很簡單的測試 (例如好不好用、美不美觀、空間感、距離感、舒適度等等), 而且測試資料又都是事先義好的, 不像人工測試時可以做到所謂 Ad Hoc 式的即興測試。我認為最好的測試方式, 就是人工與機器測試並行, 一開始就讓 QA 介入以撰寫單元測試、製訂測試標準、決定並維護測試方法與測試資料, 如此才能確實維護產品的品質。

要在 VS 中建立單元測試, 你可以先建立一個類別, 然後在 IDE 視窗中任意地方按下滑鼠右鍵, 再選「Create Unit Tests」。接著, VS 會自動附加一個單元測試專案到你目前的方案中, 並且自動建立好程式樣板, 裡面已經將所需的大部份架構寫好了, 你唯一要做的, 就是把範例程式改成你所要的。

接下來, 你可以尋找 "Additional test attributes" 這個被標註起來的區域, 把裡面 MyClassInitializer 這段程式的標註消去, 然後在裡面撰寫一些測試程式開始之前必須進行的準備動作。在我的程式中, 由於我的原始程式中必須讀取幾個 XML 檔案, 所以我必須在每次測試程式開始執行之前檢查並準備檔案及測試資料。如果你的程式不需事先準備什麼東西, 你也可以跳過這一段。

然後, 你可以把樣板中既有的程式做一些修改, 寫成類似下列的樣子:

/// <summary>
///A test for read() - Return all records
///</summary>
[TestMethod()]
public void readTest()
{
    Johnny.dlQuestion target = new Johnny.dlQuestion();
    System.Data.DataTable actual = target.read();
    If (actual == null)
         Assert.Inconclusive("dlQuestion.read() returns null.");
    Assert.IsTrue(actual.Rows.Count > 0);   
}

很可惜的, 上述技巧只適用於 VS2008 (含)之前。如果你使用的是新版的 VS (例如 VS2013), 那麼你必須手動建立一個測試專案。不過, 其實原理都是差不多的。我建議你觀看 Channel 9 上的影片 "Getting Started with Unit Testing Part 1" 和 "Getting Started with Unit Testing Part 1" (最好選擇 "High Quality MP4" 畫質)。這兩段影片使用的是很簡單的英語; 偶爾訓練一下英語聽力也不錯。

基本上, 單元測試程式就是這麼簡單; 你需要關心的要件還真的不多。首先, 記得最上面那一行 [TestMethod()] 這一絕對不能省, 否則待會執行批次測試時就不會被執行到。其次, 請注意 Assert 這個類別。這個類別下有幾個常用的方法, 例如 AreEqual, IsTrue, IsFalse, Inconclusive 等等。AreEqual 和 IsTrue 會傳回 True/False, 決定該次單元測試的 Pass/Fail。而 Inconclusive 則代表判定為 Fail。顧名思義, 這幾個方法的意義應該不難理解。

在 Assert 類別之下的幾個方法中, 有些方法提供第二個參數 message, 你可以在這個字串中寫入額外的除錯訊息。當我們從測試總管中執行各個單元測試時, 萬一某個測試發生錯誤, 我們可以很方便地透過這個參數所提供的資訊, 找到發生錯誤的原因。如下例:

Assert.IsTrue(g2.Equals(g1),
                string.Format("Equals() failed: g1 is '{0}', g2 is '{1}'",
                g1.Dump(), g2.Dump())); // Equals

如果你從未沒有開啟過測試總管的話, 你可以從 VS 上方「測試」功能表項目裡找到「視窗」、「測試總管」並開啟它。

沒有留言:

張貼留言