這個系列,請見
單元測試(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> ,回傳一個錯誤發生次數的整數值也可以。重點是「程式的可測試性」。我們寫出的程式必須具備可被測試的特性,來確保程式的品質。
客戶需求是「錯誤發生時,必須仍繼續匯入」,原始的程式已經滿足了客戶需求。但程式的「可測試性」這個需求尚未滿足啊!因此我們再因應「可測試性」的需求再修改程式。
大略地整理需求的分類
- 客戶需求:來自客戶原始的商業需求。這一點無庸置疑地一定要滿足。
- 品質需求:例如安全性、效能、可管理性等。這一點也廣泛地被接受,但客戶未必關心。客戶未必關心的事,當然有些時候就偷懶啦。
- 測試需求:這裡就是我們強調的事情。程式需要具備「可被測試」的特性,尤其是可被「自動化測試」,以利回歸測試的進行。
沒有留言:
張貼留言