重構
你不能沒有保險索
書中提及的針對軟體系統進行大範圍的、大幅度改動、就會造成毀滅性的結果,很多人不敢嘗試系統重構的原因。
在筆者實務上很常見系統的祖產是家常便飯,因為這種系統改動幅度風險相當高,即便採用【小步快跑】也一定有風險…
在書上提及的部分:這個保險索就是重構後的正確測試方法。
不改變軟體的外部行為,功能層面來看以下為:
- 重構前,使用者什麼樣的功能,重構應當提供同樣的功能
- 重構前,輸入什麼樣的請求得到運算結果,重構後也應得到相同的結果
筆者曾經在改寫vb 6轉移.net API的時候就會將過程的部分搬移,先將邏輯搬移,確保程式的輸入,輸出是對的,在資料庫寫入的結果,舊程式跑過一次與新程式跑過,兩者比對,測試不同情境也沒問題的時候先上線在開始提取類別,提取介面,反覆的測試,讓後續接手維護成本沒這麼高,不改寫的狀況下,一堆人搶那唯一windows 98的環境去vb 6 debug…成本時間浪費相當高…而且也不好debug…
系統重構測試方法
- 系統測試
- 單元測試
系統測試:透過QTP進行簡單錄製、重構前透過需求確認系統的現有功能,根據現有功能設計測試案例,進入系統,,確保每個測試都通過,並錄製QTP。
註記:每完成一次重構就手動測試或執行QTP腳本,保證測試通過,若不通過,找尋問題並解決或者退到上次的測試版本,重構失敗。
不要過早寫自動化測試程式,它不一定節省時間,可能花費更多,初期重構可以採用手動測試或QTP錄製測試進行。
遺留系統共同的特點
- 程式碼亂
- 沒有清晰介面
- 程式耦合度高
- 相互依賴嚴重
撰寫自動化測試程式前
確保測試程式已經與web容器與其他設備驅動程式相關解耦合。
其實筆者解讀是例如像DB存取驅動或API及廠商提供SDK…等都要進行解耦合。
不合適自動化測試:就是存取資料庫程式,因為要跟程式要跟資料庫互動,結果跟資料庫有關,因為會可能改狀態或是環境問題等…跟資料庫存取部分就要進行Mock。
當進行【系統重構】就要把資料庫存取相關從業務邏輯抽離出來寫入到DAO,在C#來說應該是Repository Layer,最後Mock的Repository Layer不會存取資料庫。
總結:透過手動測試和OTP錄製方式進行重構,滿足自動化測試條件後,才能進行自動化測試。
自動化測試:關於Nunit
- 在Web Request,Reponse 和 Session和Cookie不太合適用Mock
- 自動化測試程式比較忌諱存取資料庫,因為下次執行不見得成功,資料庫不穩定結果會隨時變更,唯一可以做的就是Mock Data Layer(筆者常用的Repository層)
總結:並不是所有程式都合適加入自動化測試。
這個章節在講加入測試的部分,透過重構之後加入測試專案,用Nunit的套件,筆者是用.net 所以把範例有調整過以C#為主。
而根據三種情境,進行測試早安、午安、晚安的三種情境….
工具是採用NUnit
筆者依據這個Sample調整改寫我要的情境,依據前面的重構再加入時間的Library,接近Java的範例
/// <summary>
/// A utility about time.
/// </summary>
public class DateUtil
{
/// <summary>
/// Get current hour of day.
/// </summary>
/// <param name="now"></param>
/// <returns>
/// current hour of day
/// </returns>
public int getHours(DateTime now)
{
Calendar calendar = new Calendar();
calendar.setTime(now);
return calendar.get(calendar.HOUR_OF_DAY);
}
/// <summary>
/// Get current Datetime
/// </summary>
/// <param name="year"></param>
/// <param name="month"></param>
/// <param name="day"></param>
/// <param name="hh"></param>
/// <param name="mm"></param>
/// <param name="ss"></param>
/// <returns></returns>
public DateTime createDate(int year, int month, int day, int hh, int mm, int ss)
{
return new DateTime(year, month, day, hh, mm, ss);
}
}
/// <summary>
/// The Refactoring's Hello-world program
/// author eddie
/// </summary>
public class HelloWorld
{
/// <summary>
/// Say hello to everyone.
/// </summary>
/// <param name="now"></param>
/// <param name="user"></param>
/// <returns>
/// the words what to say
/// /// </returns>
public string sayHello(DateTime now, string user)
{
var dateUtil = new DateUtil();
int hour = dateUtil.getHours(now);
var greeting = new Greeting();
if (hour >= 6 && hour < 12)
return "Hi, " + user + "." + "Good morning!";
else if (hour > 12 && hour < 19)
return "Hi, " + user + "." + "Good afternoon!";
else
return "Hi, " + user + "." + "Good night!";
}
}
開始撰寫三個測試情境
[TestFixture]
public class HelloTest
{
public DateUtil dateUtil;
public HelloWorld helloWorld;
[SetUp]
public void Setup()
{
dateUtil = new DateUtil();
helloWorld = new HelloWorld();
}
[Test]
public void testSayHelloInTheMorning()
{
DateTime now = dateUtil.createDate(2013, 9, 7, 9, 23, 11);
string user = "張三";
string result = "";
result = helloWorld.sayHello(now, user);
Assert.AreEqual("Hi, 張三.Good morning!",result);
}
[Test]
public void testSayHelloInTheAfternoon()
{
DateTime now = new DateUtil().createDate(2013, 9, 7, 15, 7, 11);
string user = "張三";
string result = "";
result = helloWorld.sayHello(now, user);
Assert.AreEqual("Hi, 張三.Good afternoon!", result);
}
[Test]
public void testSayHelloInTheAtNight()
{
DateTime now = new DateUtil().createDate(2013, 9, 7, 21, 30, 11);
string user = "張三";
string result = "";
result = helloWorld.sayHello(now, user);
Assert.AreEqual("Hi, 張三.Good night!", result);
}
}

加入測試之後,接者使用者出現需求變化要編寫所謂的Happy New Year、9月份暑假

接者就要開始跑測試,為什麼測試會出錯,是因為需求已經出現變化,所以必須要為每一個程式編寫測試案例,讓他全部Pass
總結:從這個例子的演練過來會變成以下的步驟是
- 第一步:調整程式順序、追加註解、進行分段、並修改相關變數與命名,以利閱讀,因為此次加入測試程式之後,可以編譯,雖然程式內部有調整、但是外部沒變化、重構成功可以進行後續工作。
- 第二步:提取方法在SayHello()分別提出getFirstGreeting()和getSecondGreeting()和getHour()、在執行測試程式。
- 第三步:提取類別,分別在getFirstGreeting()和getSecondGreeting()形成GreetingToUser和GreetingAboutTime完成之後、測試通過。
- 第四步:當我們需求出現變化,還要依據不同日期判斷是否是節日狀況,我們採用兩頂帽子進行開發【首先先不引入新的需求,先修改原程式,適應新需求,提煉出DateUtil,增加getHour(),getMonth,getDate(),完成重構後測試通過。
當如果其中一個測試程式沒辦法pass,就得調整測試程式,為每一個情境寫測試,讓測試通過。
關於兩頂帽子:https://www.ithome.com.tw/voice/115057
元哥的筆記