這個系列,請見
單元測試(1): 什麼是單元測試
單元測試(2): 單元測試的好與壞
單元測試(3): 單元測試的好與壞2
上次首次提到了「可測試性」的重要,這一次舉另外一個例子。
這段程式,是一個將一段xml寫到一個特定目錄的檔案。
using System;
using System.IO;
using System.Xml.Linq;
namespace ClassLibrary2
{
public class Class1
{
public void WriteXmlToFile(DateTime date)
{
XElement el = new XElement("Order", new XElement("OrderDate", date));
File.WriteAllText(@"c:\temp\result.xml", el.ToString());
}
}
}
相同的,如下的測試程式,要如何聲明(Assert)是否正確呢?
using System;
using ClassLibrary2;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace TestProject1
{
[TestClass()]
public class Class1Test
{
[TestMethod()]
public void WriteXmlToFileTest()
{
var target = new Class1();
target.WriteXmlToFile(new DateTime(2009, 1, 1));
}
}
}
真的沒辦法嗎?我們也可以將輸出的結果檔案讀出來。並與預期的結果比對。當然,這樣是不好的。
BAD
using System;
using ClassLibrary2;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.IO;
namespace TestProject1
{
[TestClass()]
public class Class1Test
{
[TestMethod()]
public void WriteXmlToFileTest()
{
var target = new Class1();
target.WriteXmlToFile(new DateTime(2009, 1, 1));
string result = File.ReadAllText(@"c:\temp\result.xml");
Assert.AreEqual(@"
2009-01-01T00:00:00
", result);
}
}
}
這樣有什麼不好呢?除了前次提到的與檔案系統相關外,又有什麼不好呢?目錄指定的路徑是客戶指定的,除可以改設定到 config 檔上,其餘沒有什麼好談的。
其實,這一段的確是可以測試的。但需要強調的事情是:可測試性不佳。
如果客戶要求的是將 xml 資料以 email 或 web service 方式傳出去呢?那要如何測試該段 xml 是正確的呢?難道要去讀對方 web service 是否收了了訊息,或 email server 是否收了 email?
我的解答如下:改變 method 的 signature
using System;
using System.IO;
using System.Xml.Linq;
namespace ClassLibrary2
{
public class Class1
{
public string WriteXmlToFile(DateTime date)
{
XElement el = new XElement("Order", new XElement("OrderDate", date));
string result = el.ToString();
File.WriteAllText(@"c:\temp\result.xml", result);
return result;
}
}
}
測試程式碼如下:
using System;
using ClassLibrary2;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.IO;
namespace TestProject1
{
[TestClass()]
public class Class1Test
{
[TestMethod()]
public void WriteXmlToFileTest()
{
var target = new Class1();
string result = target.WriteXmlToFile(new DateTime(2009, 1, 1));
Assert.AreEqual(@"
2009-01-01T00:00:00
", result);
}
}
}
只有一點小改變:該method 回傳 xml 的結果。
質疑聲:「什麼?為什麼要回傳 xml 呢?又不需要!只會浪費時間,效能變差」
這一點,真的不好回答。回到一個問題,什麼是需求?什麼樣的事情才是需求?
我們常將需求分成「功能需求」與「非功能需求」。功能需求是指客戶真的需要的功能。非功能需求又可再細分安全性、效能、可維護性、…等。
為了安全性,會加上一堆的 System.Security 的類別與物件。
為了效能,會使用多執行緒的方式加快程式執行速度、加上效能物件以量測正式機上的效能。
為了可維護性,更會以物件導向、再搞出Multi-layer, SOA, MVC 等架構。
那「可測試性」不也是一種「非功能需求」嗎?為了「可測試性」,當然也可以將某一 method 由 void 改成回傳 string 啊!
結論
為了可測試性,可以改變程式的 signature。