處理 legacy code 需要什麼工具?除了編輯器(或 IDE)和建構系統之外,你還需要一個測試框架。如果你的語言有自動化重構工具,那會更有幫助。
自動化重構工具#
手動重構沒問題,但當你有工具代勞時,效率會大幅提升。自動化重構工具的歷史可追溯至 1990 年代 Bill Opdyke 在 C++ 上的工作,以及 John Brant 和 Don Roberts 在 Illinois 大學開發的 Smalltalk 重構瀏覽器。
重構工具的注意事項#
重構(Refactoring) 的定義(引自 Martin Fowler):對軟體內部結構進行的修改,使其更易於理解和修改,同時不改變其既有行為。
重構工具應該要驗證變更不會改變行為,但實際上有些工具並不完善。使用時需注意:
- 了解工具開發者對安全性的說明
- 自行執行簡單的正確性測試(例如:提取方法時,若方法名稱與基底類別衝突,工具是否會警告?)
- 如果工具無法保證行為不變,就不要使用自動化重構
自動化重構不能完全取代測試。 即使工具聲稱安全,也可能在邊界情況下引入微妙的行為變化。例如,將
getValue()的回傳值內聯到迴圈中,可能會讓該方法被呼叫多次而非一次,改變了副作用的次數。最佳實務是在使用自動化重構前先有測試。但在 legacy code 中,你可以先用工具的 Extract Method 等功能把程式碼整理到更可測試的狀態——前提是你信任工具在這些操作上的正確性。
Mock Objects#
Legacy code 工作中最大的問題之一是相依性。當我們想單獨測試一段程式碼時,需要用某些東西替代真實的相依物件——這些替代品就是 Mock Objects。
多種 Mock Object 函式庫可供使用,它們讓你能快速建立替代物件,專注於測試邏輯本身。
單元測試框架#
xUnit 框架#
最有效的測試工具是 xUnit 測試框架,由 Kent Beck 在 Smalltalk 中開創,後由 Kent Beck 和 Erich Gamma 移植到 Java。xUnit 的核心特點:
- 讓程式設計師用開發所用的語言撰寫測試
- 所有測試隔離執行
- 測試可以分組為測試套件(Test Suites),按需執行
JUnit(Java)#
在 JUnit 中,透過繼承 TestCase 撰寫測試:
public class FormulaTest extends TestCase {
public void testEmpty() {
assertEquals(0, new Formula("").value());
}
public void testDigit() {
assertEquals(1, new Formula("1").value());
}
}JUnit 的關鍵機制:
setUp():在每個測試方法執行前呼叫,用於建立共用的測試物件tearDown():在每個測試方法執行後呼叫,用於清理資源- 每個測試方法在獨立的物件實例中執行,測試之間不會互相影響
CppUnitLite(C++)#
作者為 C++ 開發的輕量測試框架,使用巨集簡化測試撰寫:
#include "testharness.h"
#include "employee.h"
#include <memory>
using namespace std;
TEST(testNormalPay, Employee)
{
auto_ptr<Employee> employee(new Employee("Fred", 0, 10));
LONGS_EQUALS(400, employee->getPay());
}NUnit(.NET)#
針對 .NET 平台的測試框架,使用屬性(Attributes)標記測試類別和方法,運作方式與 JUnit 極為相似。
通用測試框架#
FIT(Framework for Integrated Tests)#
由 Ward Cunningham 開發,FIT 讓你可以在文件中嵌入表格形式的測試,FIT 框架會自動執行這些測試。它的獨特之處在於促進了撰寫軟體的人與指定需求的人之間的溝通。
Fitnesse#
Fitnesse 本質上是一個架設在 Wiki 上的 FIT。由 Robert Martin 和 Micah Martin 開發,支援階層式的測試頁面,便於團隊協作。
本書中所有描述的測試工具都是免費且開源的。選擇工具時,最重要的是它能讓你快速撰寫並執行測試,而不是花哨的功能。