2010年4月9日 星期五

單元測試(6):可測試性2

這個系列,請見
單元測試(1): 什麼是單元測試
單元測試(2): 單元測試的好與壞
單元測試(3): 單元測試的好與壞2
單元測試(4): 單元測試的好與壞3
單元測試(5): 可測試性

需求

今天在 Review 同事的程式時,發現了不可告人的秘密。程式片段如下。

   1: using System;
   2: using System.IO;
   3:  
   4: namespace ConsoleApplication1
   5: {
   6:   class Class1
   7:   {
   8:     public void Do()
   9:     {
  10:       string[] lines = File.ReadAllLines(@"c:\temp\myfile.txt");
  11:       foreach (var line in lines)
  12:       {
  13:         try
  14:         {
  15:           ParseLineAndSaveToDb(line);
  16:         }
  17:         catch (Exception e)
  18:         {
  19:           SendMail(e.Message);
  20:         }
  21:       }
  22:     }
  23:   }
  24: }

用意大概是:讀取客戶上傳的檔案,解析每一行資料後,儲存到資料庫內。當一行資料有錯誤,仍繼續執行匯入。錯誤的訊息以寄信的方式寄給負責人處理。

問題

這段程式看來完美,也合乎客戶/長官的需求。但詭異的是,當我建立Do() 的單元測試後,該單元測試從未發生例外。
天啊,因此這段程式執行起來永遠不會出錯。永不會出錯的程式一定是好程式??

當然紙包不住火的,當程式執行結果不如預期時,就會回過頭來找 bug。這樣一來就很難找了。單元測試建立起來後無法進行自動測試,因為程式沒有出錯的機會,單元測試 3A pattern 中的 Assert 沒有發揮空間。

為了讓單元測試有機會進行實質的 Assert (斷言),我們必須對程式進行修改,而且必須符合客戶需求。修改後的程式如下

   1: using System;
   2: using System.IO;
   3: using System.Collections.Generic;
   4:  
   5: namespace ConsoleApplication1
   6: {
   7:   class Class1
   8:   {
   9:     public List<string> Do()
  10:     {
  11:       string[] lines = File.ReadAllLines(@"c:\temp\myfile.txt");
  12:       List<string> errors = new List<string>();
  13:       foreach (var line in lines)
  14:       {
  15:         try
  16:         {
  17:           ParseLineAndSaveToDb(line);
  18:         }
  19:         catch (Exception e)
  20:         {
  21:           SendMail(e.Message);
  22:           errors.Add(e.Message);
  23:         }
  24:       }
  25:       return errors;
  26:     }
  27:   }
  28: }

注意到Do() 加上了回傳值 List<string>,用來回傳發生的錯誤訊息。單元測試時,就可根據回傳錯誤長度來判斷是否發生過錯誤。

   1: [TestMethod()]
   2: public void DoTest()
   3: {
   4:   Class1 target = new Class1();
   5:   List<string> errors = target.Do();
   6:   Assert.AreEqual(0, errors.Count);
   7: }

結論

當然解法不一定要回傳 List<string> ,回傳一個錯誤發生次數的整數值也可以。重點是「程式的可測試性」。我們寫出的程式必須具備可被測試的特性,來確保程式的品質。

客戶需求是「錯誤發生時,必須仍繼續匯入」,原始的程式已經滿足了客戶需求。但程式的「可測試性」這個需求尚未滿足啊!因此我們再因應「可測試性」的需求再修改程式。

大略地整理需求的分類

  1. 客戶需求:來自客戶原始的商業需求。這一點無庸置疑地一定要滿足。
  2. 品質需求:例如安全性、效能、可管理性等。這一點也廣泛地被接受,但客戶未必關心。客戶未必關心的事,當然有些時候就偷懶啦。
  3. 測試需求:這裡就是我們強調的事情。程式需要具備「可被測試」的特性,尤其是可被「自動化測試」,以利回歸測試的進行。

沒有留言:

Share with Facebook