傳統 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) {}
}
}
}
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...) |
prepareCall("{call LUCK_LOAD_COMMON(?,?)}") 與 prepareCall("{call LUCK_LOAD_COMMON(?,?,?)}") 視為不同呼叫
anyString()、eq("exact")、aryEq(byte[]) 等 Matcher 鬆綁參數檢查cstmt.close() 無回傳值,直接呼叫即記錄
expect(mockCstmt.close())(編譯錯誤)mockCstmt.close(); 後接 replay()times(n) 或 atLeastOnce()
expect(mockCstmt.setString(anyInt(), anyString())).times(3);
expect(mockConn.prepareCall(anyString())).andThrow(new SQLException("DB down"));
Capture<String> capturedSql = newCapture();
expect(mockConn.prepareCall(capture(capturedSql))).andReturn(mockCstmt);
replay(mockConn);
// ... 執行測試 ...
verify(mockConn);
assertTrue(capturedSql.getValue().contains("LUCK_LOAD_COMMON"));
| 特性 | EasyMock | Mockito |
|---|---|---|
| API 風格 | 記錄-回放 (傳統) | 行為驗證 (現代、更直觀) |
| 參數匹配 | 嚴格預設 | 寬鬆預設 |
| Void 方法 | 直接呼叫記錄 | doNothing().when(mock).voidMethod() |
| 部分 Mock | 支援但較繁瑣 | spy() 原生支援 |
| 適用場景 | 舊專案維護、嚴格呼叫順序驗證 | 新專案首選、BDD 風格測試 |
Connection, PreparedStatement),不 Mock 具體類別@Rule 或 @After 確保 verify() 執行掌握 EasyMock 對 JDBC 程式碼的測試技巧,能大幅提升資料存取層測試覆蓋率,同時保持測試執行速度與環境獨立性。