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

使用 EasyMock 進行 JDBC 程式碼單元測試實戰指南

     編輯:Java 相關知識

為什麼需要 Mock JDBC 測試

傳統 JDBC 程式碼高度依賴資料庫連線,導致單元測試執行緩慢、環境依賴強、難以模擬例外情境。EasyMock 透過「記錄-回放」模式,讓開發者在記憶體中模擬 Connection、CallableStatement 等介面行為,實現真正的單元測試隔離。

待測試目標程式碼

public String getNumber() {
    CallableStatement cstmt = null;
    try {
        cstmt = _conn.prepareCall("{call LUCK_LOAD_COMMON(?,?)}");
        cstmt.setString(1, "1");
        cstmt.registerOutParameter(2, java.sql.Types.VARCHAR);
        cstmt.executeUpdate();
        return cstmt.getString(2);
    } catch (Exception e) {
        GxDebug.logException(e);
        e.printStackTrace();
        return null;
    } finally {
        if (cstmt != null) {
            try { cstmt.close(); } catch (Exception e) {}
        }
    }
}

EasyMock 測試程式碼完整實作

import static org.easymock.EasyMock.*;
import org.junit.Test;
import java.sql.*;

public class JdbcDaoTest {
    @Test
    public void testGetNumber() throws Exception {
        // 1. 建立 Mock 物件
        Connection mockConn = createMock(Connection.class);
        CallableStatement mockCstmt = createMock(CallableStatement.class);
        
        // 2. 記錄期望行為 (Recording Phase)
        expect(mockConn.prepareCall("{call LUCK_LOAD_COMMON(?,?)}"))
            .andReturn(mockCstmt);
        
        mockCstmt.setString(1, "1");
        mockCstmt.registerOutParameter(2, Types.VARCHAR);
        expect(mockCstmt.executeUpdate()).andReturn(1);
        expect(mockCstmt.getString(2)).andReturn("5,4,3");
        mockCstmt.close();
        
        // 3. 進入回放模式
        replay(mockConn, mockCstmt);
        
        // 4. 注入 Mock 連線並執行測試
        dao.setConnection(mockConn); // 假設 DAO 提供 setter 注入
        String result = dao.getNumber();
        
        // 5. 驗證結果與行為
        assertEquals("5,4,3", result);
        verify(mockConn, mockCstmt);
    }
}

核心概念解析:記錄-回放模式

階段動作關鍵 API
記錄期在 Mock 物件上呼叫預期方法,設定回傳值/拋出例外expect(...).andReturn(...), expect(...).andThrow(...)
切換期宣告記錄結束,進入驗證模式replay(mockObj...)
回放期執行實際測試程式碼,Mock 物件按記錄順序驗證呼叫實際業務邏輯執行
驗證期確認所有預期呼叫均已發生verify(mockObj...)

常見陷阱與解決方案

  1. 參數匹配嚴格性:prepareCall("{call LUCK_LOAD_COMMON(?,?)}") 與 prepareCall("{call LUCK_LOAD_COMMON(?,?,?)}") 視為不同呼叫
    • 解決:使用 anyString()、eq("exact")、aryEq(byte[]) 等 Matcher 鬆綁參數檢查
  2. Void 方法記錄:cstmt.close() 無回傳值,直接呼叫即記錄
    • 錯誤寫法:expect(mockCstmt.close())(編譯錯誤)
    • 正確寫法:mockCstmt.close(); 後接 replay()
  3. 多次呼叫相同方法:使用 times(n) 或 atLeastOnce()
    expect(mockCstmt.setString(anyInt(), anyString())).times(3);
  4. 例外情境模擬:
    expect(mockConn.prepareCall(anyString())).andThrow(new SQLException("DB down"));

進階技巧:Capture 引數驗證

Capture<String> capturedSql = newCapture();
expect(mockConn.prepareCall(capture(capturedSql))).andReturn(mockCstmt);
replay(mockConn);
// ... 執行測試 ...
verify(mockConn);
assertTrue(capturedSql.getValue().contains("LUCK_LOAD_COMMON"));

EasyMock vs Mockito 選型建議

特性EasyMockMockito
API 風格記錄-回放 (傳統)行為驗證 (現代、更直觀)
參數匹配嚴格預設寬鬆預設
Void 方法直接呼叫記錄doNothing().when(mock).voidMethod()
部分 Mock支援但較繁瑣spy() 原生支援
適用場景舊專案維護、嚴格呼叫順序驗證新專案首選、BDD 風格測試

最佳實踐總結

  • [ ] 只 Mock 介面(Connection, PreparedStatement),不 Mock 具體類別
  • [ ] 測試專注單一邏輯單元,避免過度模擬導致測試脆弱
  • [ ] 使用 @Rule 或 @After 確保 verify() 執行
  • [ ] 整合測試層改用 Testcontainers / H2 內嵌資料庫驗證真實 SQL
  • [ ] 避免測試實作細節(如具體 SQL 字串),改驗證業務結果

掌握 EasyMock 對 JDBC 程式碼的測試技巧,能大幅提升資料存取層測試覆蓋率,同時保持測試執行速度與環境獨立性。

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