2009年9月16日 星期三

單元測試(4): 單元測試的好與壞3

這個系列,請見
單元測試(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。

沒有留言:

Share with Facebook