2012/3/6

使用 XML 當作單元測試中的測試資料來源

MSDN 對單元測試的介紹中, 對單元測試的做了基本的介紹。站在我這個 former QA 的角度來看, 一般人如果只是照著上面的簡單介紹去做單元測試, 然後就以為單元測試只不過是這樣而已的話, 未免把單元測試看得太單純。事實上「使用資料驅動的單元測試」才是真正實用的。怎麼說呢? 因為, 如果我們不是餵給測試單元很多預先知道結果的測試資料去進行測試的話, 所謂的「自動化測試」只是空談罷了。為什麼團隊裡面必須有 QA 存在? 就是因為我們需要 QA 站在開發者的對立面, 試圖去找出開發者沒注意或甚至沒想到的弱點; 有攻有防, 才能確保產品的品質。

測試者 vs. 開發者

QA 可以運用很多測試理論, 以及自己對於產品的了解和經驗, 設定不同的測試資料來檢驗軟體。我在「程式內的防呆之道」一文中有提到一些測試方面的理論, 在此就不再贅述。

順帶提一下, 我們不能要求測試人員都要會寫程式。曾經擔任客服的人、Power User、甚至任何普通人, 都比團隊內的程式設計師更適合擔任 QA 這個角色。如果讓具有利害關係的開發人員來擔任 QA 的角色, 那實在只有「請鬼拿藥單」能形容這種狀況了。

如果 QA 能自己寫單元測試, 那是最好了。但是如果不擅長寫程式的話(雖然仍必須有程式設計的基礎, 才有辦法做白箱測試), 那麼仍然要借助開發人員來撰寫。但是測試資料就應該是由 QA 來做, 以發動所謂「資料驅動」(Data-driven) 的測試。

雲深不知處的文件

在「使用資料驅動的單元測試」一文中, 我們可以看到對於 TestContext 的簡單介紹。但是, 對於一個不是領悟力超強的普通人而言, 恐怕難以理解這段言簡意賅、惜話如金的說明文字到底要教會我們什麼。事實上, TextContext 除了可以提供一些測試資訊之外, 還有一個重要的功能, 就是讓我們可以把測試資料取出, 做為單元測試的材料。所謂的「資料驅動」這四個字就是這麼來的。

撰寫技術文件的人時常有個毛病, 就是把其它不重要或者瑣碎的細節寫得洋洋灑灑, 若有其事, 卻把重點中的重點輕輕的帶過, 或者甚至略而不提。從最不客氣的角度來講, 就是非得這樣才能顯得自己的莫測高深。好像一定要把文章寫得讓人看不懂、摸不清楚頭緒, 如此才能彰顯自己的功力似的。然而, 如果連官網上面的技術文件都來搞這麼一套, 我只能說這種技術文件只不過是拿來昭告大眾說「你看, 我寫了呀! 只是你自己太笨看不懂而已,怪誰呢?」

所以, 我再強調一次, 透過使用 TestContext, 讓我們可以把測試資料取出, 做為單元測試的材料。這個類別的最重要功能就在這裡, 文件裡把這個前題用婉轉的方式表達了。他確實有寫, 只不過使用了模擬兩可、若有似無、不痛不癢的官腔官調。如果你不能從文件中自行領悟這個功能的最基本目的, 是你自己笨。

在上述文件中, 我們可以看到讀取資料庫跟 Excel 的簡單範例。事實上, 即便它提供了這兩種範例, 它還是省略了一個最重要的重點, 那就是這些測試資料會自動地一筆一筆帶出來執行, 不需要你自己寫迴圈。再一次, 它又來考驗讀者的領悟力了。

其實, 只要你照著範例做過一次, 你也可以看出以上兩點。但是我不能理解, 為什麼像這種最重要的前題只能暗示而不能明說呢? 我一天到晚在論壇上試圖猜懂發問者不清不楚的發問, 也一天到晚在各大官網上查閱避重就輕的文件; 我可以諒理論壇上的發問者也許侷限於文筆與表達能力, 我卻不能諒解官網文件撰寫者那種應付了事的態度(像是標題明明寫的是 "How to...", 結果讓諸者看完比還沒看時更不懂的那種文件)。

幸好, 如果是針對 MSDN 而言, 我覺得還是有很多寫得不錯、有用過心的文件存在。或許是執筆人的心態各有不同吧!

以 XML 當作資料來源

在我之前待過的公司裡, 測試資料都放在資料庫裡, 存取都很方便。但是我現在面臨一個不方便使用資料庫或者  Excel 的情況。

如果測試資料非常大量的話, 資料庫當然是最好的選擇。但是如果資料量沒那麼大, 那麼使用 XML 或者 CSV 檔案也是不錯的選擇。在這裡, 我要示範使用 XML 來存放測試資料的做法。

首先, 請把要被測試的程式和單元測試建立好。如果你不熟悉單元測試的話, 可以參考「[入門] 在 Visual Studio 中建立及進行單元測試」一文裡面的介紹。

接著, 請自己建立一個 XML 檔案, 其內容如下例:

<?xml version="1.0" encoding="utf-8" ?>
<AltColorConverter>
  <Data>
    <Input>0, 0, 0, 0</Input>
    <Expected>0, 0, 0, 0</Expected>
    <Result>1</Result>
  </Data>
  <Data>
    <Input>255, 255, 255, 255</Input>
    <Expected>255, 255, 255, 255</Expected>
    <Result>1</Result>
  </Data>
  (以下省略)
</AltColorConverter>

各位可以看到我建立了三個欄位, 分別是 Input, Expected 和 Result, 分別代表要被測試的資料、應該出現的結果, 以及應該為真或為偽。

我的邏輯有點稍為複雜, 其判斷的方式是如果 (Input == Expected) == Result 才能通過, 否則不能通過。因為有時候 (Input != Expected) 才是對的。

你不一定要依循這種方式。一般而言, 使用 Input 和 Expected 來做判斷也已經很足夠了。

當你要開始測試之前, 記得把 TestData.xml 這個 XML 檔案拷貝到專案 bin\Debug 或 bin\Release 之下, 測試程式才能取得資料。

接著, 如下圖所示, 在測試清單編輯器(請從 VS 最上方「測試」、「視窗」表單中選取)中選取你的測試程式(例如下例中的 ConvertFromStringTest()), 然後按下 F4 進入屬性視窗, 找到「資料連接字串」屬性, 按下 ... 按鈕, 選擇 XML, 再找到 TestData.xml 檔案的路徑。我們可以在這裡看到資料的預覽。

設定好資料之後, 你應該能在測試程式的上方看到 DeploymentItem 這個 attribute, 上面已經標注了 TestData.xml 的路徑。

我的單元測試程式如下:

/// <summary>
/// ConvertFromString 的測試
///</summary>
[DeploymentItem("UnitTests\\TestData.xml"), DataSource("Microsoft.VisualStudio.TestTools.DataSource.XML", "|DataDirectory|\\TestData.xml", "Data", DataAccessMethod.Sequential), TestMethod()]
public void ConvertFromStringTest()
{
    string input = (string)TestContext.DataRow["Input"];
    Color actual =  AltColorConverter.ConvertFromString(input);
    string expectedStr = (string)TestContext.DataRow["Expected"];
    Color expected = convertStrToColor(expectedStr);
    bool shouldBeTrue = TestContext.DataRow["Result"].ToString() == "1";
    Assert.AreEqual((expected == actual), shouldBeTrue);
}

/// <summary> 
/// Convert the input string (in "A, R, G, B" formmat) to Color 
/// </summary>
private Color convertStrToColor(string expectedStr)
{
    string[] values = expectedStr.Split(',');
    int a = int.Parse(values[0]);
    int r = int.Parse(values[1]);
    int g = int.Parse(values[2]);
    int b = int.Parse(values[3]);
    return Color.FromArgb(a, r, g, b);
}

如程式所示, 要被測試的類別叫做 AltColorConverter, 方法叫做 ConvertFromString(), 它的目的是把 "255, 255, 255, 255" 這種格式的字串轉換成 ARGB(255, 255, 255, 255) 格式的 Color 型別。

我們可以看到, 使用 TestContext.DataRow["Input"] 可以取得 XML 中的一筆 Input 欄位。使用同樣的方法可以再得到 Expected 和 Result 欄位。讀取的資料是 object 型別, 經過轉型之後就可以拿來做其它運用。其中 convertStrToColor() 是我在單元測試程式中另外寫的一個方法, 專門用來把讀到的 Expected 資料轉型成 Color 型別。當然, 在這裡, convertStrToColor() 這個方法是絕對不容許有 bug 的, 否則整個單元測試就失去意義了。

如果你遇到提示說 DataRow 無法使用 [] 索引之類的錯誤, 那麼很有可能是因為單元測試專案中並未引入 System.Data 作為參考的緣故。我也不知道為什麼有時候會出現這種問題, 不過你只需要把 System.Data 組件加入參考就行了。

接著, 請在測試清單編輯器中按下最上方工具列中左邊的測試按鈕, 即可進行測試。請注意, 你寫入 TestData.xml 中的每一筆測試資料都會被自動帶出來。只要有一筆資料測試失敗, 測試結果就會顯示失敗。

如果你要對你的測試程式進行偵錯的話, 請不要在測試清單編輯器中進行測試, 而是應該進入測試程式, 再按下 F5 進行偵錯。對測試程式的偵錯動作和普通程式沒有太大差異; 中斷點、監看式等等功能都可以使用。

實際案例

如果你想看看別人的 Unit Test 是怎麼寫的話, 我可以提供一個完整的方案, 附上全部原始檔及測試資料。事實上, 本文中的範例都是從那個方案中借來的, 只不過我把它簡化過了。

AltColorConverter 是我發表在 CodePlex 上的一個專案。這個專案很適合用來做為 Unit Test 的範例, 因為這個 C# 專案中基本上只有一個公用方法而已, 其餘部份幾乎都是測試程式和測試資料。

不過這個專案裡面的程式和本文中的範例還是有一些不同, 或許你必須做一些額外的功課才能體會。

此外, 我在 AltConverter 專案中做了一些不是每個人都會做的事情, 我最好交待一下, 否則其他朋友恐怕不容易馬上看出來。那就是我是把測試資料全部放在 Unit Test 專案下的 \TestData 資料夾裡面。如此, 測試者將專案簽出後, 就可以很方便的在 Visual Studio 裡面編輯測試資料, 既不需要另外找地方儲存這些檔案, 也不用離開 Visual Studio。當 Unit Test 一發動, 它會把 \TestData 下所有 XML 檔案自動拷貝到 bin\Debug 或 bin\Release 資料夾裡面。所以請不要去編輯上述兩個資料夾裡面的測試資料, 因為它們會被覆蓋。

或許讀者們看了這個專案之後, 有朋友會說「對於這麼簡單的小程式, 有需要做這麼複雜的測試嗎?」

如果我自己不是程式的開發者, 而是單純的 QA 的話, 我會認為這種問題是很奇怪的。的確, 在這個專案中的 Unit Test 的部份, 無論是程式或是測試資料, 都遠比受測程式本身大得多。但是憑良心說, 由於受測程式(即 AltColorConverter)承諾了很多種字串格式的支援(請參考網頁上的文件), 對它的測試實在沒有辦法縮小。站在 QA 的立場, 在寫完這個專案之後, 我擔心的反而是測試得不夠(因為只有我一個人在做測試, 而我又是不具公正客觀立場的開發者)的問題; 它根本沒有過度測試的問題。況且, 光在撰寫測試程式和準備測試資料時, 就真的讓我找出了好幾個意想不到的 bug。

但站回開發者的角度, 誠實的說, 如果這個專案不是我個人閒暇之作, 而是公司裡必須限時完成的專案, 我恐怕真的不會有那個心力寫這麼複雜的測試程式, 更不會認真的做測試資料。

所以, 老話一句: 請個專門的 QA 來做測試吧!

參考資料:

 

沒有留言:

張貼留言