首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

在 Eclipse 中打造客製化重構功能:以 JUnit 測試遷移為例

     編輯:Java 相關知識

前言:重構在現代開發的關鍵角色

在當代軟體工程中,重構已成為不可或缺的核心實踐。它不僅能顯著減輕開發人員的維護負擔,更能大幅提升開發產能。正如 developerWorks 上 David Carew 所言:現今開發者的大部分工作聚焦於修改既有代碼而非撰寫新代碼。隨著修改與擴展的累積,軟體內部結構漸趨惡化,而重構正是在不改變外部行為的前提下,優化內部結構,使代碼更易理解、修改成本更低。

在 Java 開發環境中,Eclipse 內建的重構工具(如 Rename、Extract Method 等)已帶來實質效益:

  • 產品更健壯:修改錯誤率降低,遺漏修改機率減少,且可透過 Undo 機制回滾。
  • 效率倍增:單次重構操作即可同步完成多處代碼變更,Rename 重構即是典型範例——修改名稱時自動更新所有引用。

然而,現有重構功能難免無法滿足特定專案或團隊的獨特需求。此時,擴展 Eclipse 平台、開發客製化重構功能便成為最佳解決方案。若該功能具備通用價值,更可貢獻回 Eclipse 社區,造福廣大開發者。

實戰目標:JUnit 3 到 JUnit 4 的測試方法遷移

本文將以一個具體案例示範如何開發新重構功能:將 JUnit 3 風格的測試方法(以 test 開頭的方法名)自動遷移為 JUnit 4 風格(添加 @Test 註解)。進階需求包含支援 @Test(timeout=...) 屬性,透過嚮導頁面讓使用者決定是否加入 timeout 參數及其數值。

成果預覽:使用者體驗流程

啟動重構後,將彈出三頁式嚮導:

  1. 參數輸入頁:勾選是否需加入 timeout 參數,並設定其值(預設 500ms)。
  2. 條件檢查頁:自動執行初始條件與最終條件檢查,若皆通過則可進入下一步。
  3. 預覽頁:以直觀介面展示即將對源代碼產生的變更,使用者可逐檔確認或取消特定檔案的修改。

確認無誤按下 Finish 後,源代碼即被修改。例如:

package main;
public class TestSomething {
    @Test(timeout=500)
    public void testSomething() {
    }
}

核心架構:三大支柱與標準流程

Eclipse 重構操作由三大核心類別協作完成:

  • RefactoringWizard:提供嚮導式 UI,內建預覽頁、條件檢查頁、Undo/Redo 功能。開發者需繼承此類以注入特定 UI 頁面。
  • Refactoring:執行實際的代碼定位與修改邏輯。需繼承並實作核心演算法。
  • AST 與 ASTParser:Java 代碼解析為抽象語法樹(AST),將代碼修改轉化為樹節點的遍歷、屬性變更、節點增刪等結構化操作。

標準重構流程:

  1. 使用者選取重構目標,透過選單或按鈕觸發。
  2. 建立具體 Refactoring 實例,彈出 RefactoringWizard。
  3. Wizard 引導使用者輸入參數,呼叫 Refactoring 進行條件檢查。
  4. Refactoring 建立 AST,完成代碼定位與修改,產生 Change 物件(暫不直接寫入源碼)。
  5. Wizard 取得 Change 物件並顯示於預覽頁,待使用者確認。
  6. 使用者確認後,框架套用變更,重構完成。

步驟一:建立外掛專案

透過 File → New → Plug-in Project 建立專案 manage.annotation,選用 "Hello, World" 範本以便在 Refactor 選單加入新項目。Action 類別命名為 AnnotationManageAction。

於 MANIFEST.MF 的 Require-Bundle 加入必要依賴:

org.eclipse.jface.text
org.eclipse.ltk.core.refactoring
org.eclipse.ltk.ui.refactoring
org.eclipse.jdt
org.eclipse.jdt.core

修改 plugin.xml,擴展 org.eclipse.ui.actionSets,於 Refactor 選單的 reorgGroup 分隔符下新增 "Annotation Manage" 選單項與工具列按鈕,事件由 AnnotationManageAction 處理。

在 AnnotationManageAction 的 selectionChanged 中驗證使用者選取:僅當單一選取項為 IJavaElement 時啟用 Action,否則置灰並提示不可用。

步驟二:擴展 Refactoring 類別

建立 AnnotationRefactoring 繼承 org.eclipse.ltk.core.refactoring.Refactoring,需實作三大核心方法:

  • 建構子:接收使用者選取的 IJavaElement,向上追溯至 COMPILATION_UNIT 層級,收集所有可寫入的編譯單元。
  • checkInitialConditions:初始檢查,本範例直接回傳 RefactoringStatus.createInfoStatus("Initial Condition is OK!")。
  • checkFinalConditions:取得使用者參數後的最終檢查。呼叫 collectChanges() 掃描所有以 "test" 開頭的方法,若無符合者回傳 FATAL 錯誤,否則回傳 INFO。
  • createChange:核心邏輯,將 AST 修改封裝為 Change 物件(詳見下節)。

步驟三:運用 AST 建構 Change 物件

代碼修改有兩條路徑:IScanner+IBuffer 直接操作字串,或遍歷 AST 進行結構化編輯。本文採後者,優勢在於語義層面操作,避免字串處理的脆弱性。

建立 AST:

ASTParser parser = ASTParser.newParser(AST.JLS3);
parser.setSource(cu); // ICompilationUnit
ASTNode root = parser.createAST(null);

訪問節點收集目標方法:

private void getMethods(ASTNode cuu, final List methods) {
    cuu.accept(new ASTVisitor() {
        public boolean visit(MethodDeclaration node) {
            methods.add(node);
            return false;
        }
    });
}

插入 @Test 註解:

private boolean collectChanges(CompilationUnit root, MethodDeclaration method) {
    if (method.getName().getFullyQualifiedName().startsWith("test")) {
        AST ast = method.getAST();
        if (needTimeout) {
            NormalAnnotation na = ast.newNormalAnnotation();
            na.setTypeName(ast.newSimpleName("Test"));
            MemberValuePair pair = ast.newMemberValuePair();
            pair.setName(ast.newSimpleName("timeout"));
            pair.setValue(ast.newNumberLiteral("500"));
            na.values().add(pair);
            method.modifiers().add(0, na);
        } else {
            MarkerAnnotation na = ast.newMarkerAnnotation();
            na.setTypeName(ast.newSimpleName("Test"));
            method.modifiers().add(0, na);
        }
        return true;
    }
    return false;
}

記錄修改並產生 Change:

root.recordModifications();
// ... 修改 AST ...
TextEdit edits = root.rewrite(document, cu.getJavaProject().getOptions(true));
TextFileChange change = new TextFileChange("", (IFile) cu.getResource());
change.setEdit(edits);

若需修改多個檔案,使用 CompositeChange 封裝多個 Change 為單一物件回傳。

步驟四:擴展 RefactoringWizard 框架

Wizard 架構遵循 OCP 原則:界面(WizardPage)、容器、對話框分離,業務邏輯封裝於 Refactoring,支援動態組合頁面。

  1. 擴展 WizardPage:建立 AnnotationRefactoringWizardPage 繼承 UserInputWizardPage,於 createControl 構建 UI(Checkbox、Text 欄位),並透過 setControl(composite) 納入框架管理。使用 notifyStatus 設定頁面完成狀態,setRefactoring 將參數傳遞給 Refactoring 實例(利用 Observer 模式)。
  2. 擴展 Wizard:建立 AnnotationRefactoringWizard 繼承 RefactoringWizard,建構子載入 Refactoring,覆寫 addUserInputPages 加入自訂頁面。
  3. 啟動 Wizard:於 AnnotationManageAction.run() 中建立 Refactoring 與 Wizard 實例,透過 RefactoringWizardOpenOperation 開啟對話框(會自動執行初始檢查)。

總結

善用 Eclipse 重構框架開發客製化功能,能大幅提升開發效率與代碼品質。雖然目前 JDT 的重構支援最完善,但透過本文架構,開發者可為任何基於 Eclipse 的語言環境(C++、Python 等)補強重構能力,推動工具生態的完善。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved