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

JMock 實戰:以 Mock 對象實現單元測試完全隔離

     編輯:Java 相關知識

為什麼需要隔離測試?

被測類往往依賴其他類(DAO、Service、外部 API)。若測試時引入真實依賴:

  • 測試變成「集成測試」,運行慢、環境重、難以並行
  • 依賴類未實現或有 Bug 時,被測類無法測試 → 阻斷 TDD
  • 依賴之間循環引用,無法決定先測試誰

Mock 對象通過運行時動態代理(基於 JDK 反射),在測試中替換真實依賴,實現:

  1. 行為驗證:斷言依賴方法被正確調用(參數、次序、次數)
  2. 存根返回:指定特定輸入返回預設值,模擬各種場景(正常、異常、邊界)
  3. 完全隔離:被測類零依賴真實實現,測試極快、極穩

JMock 核心優勢

  • 純 Java 實現,無需字節碼操作庫
  • 聲明式期望定義,可讀性極強
  • 與 JUnit 無縫集成
  • 支持接口與類(需 CGLIB)Mock

實戰案例:測試 UserService 隔離 UserDAO

領域模型

// POJO,無邏輯,可不 Mock
public class User {
    private String name;
    public User() {}
    public User(String name) { this.name = name; }
    // getter/setter
}

DAO 接口(必須為接口以便 JDK 動態代理)

public interface UserDAO {
    void saveUser(User user);
    User getUser(Long id);
}

被測 Service

public class UserService {
    private UserDAO userDAO;
    public void setUserDAO(UserDAO userDAO) { this.userDAO = userDAO; }

    public void registerUser(String name) {
        User user = new User(name);
        userDAO.saveUser(user);
    }

    public User findUser(Long id) {
        return userDAO.getUser(id);
    }
}

JMock 測試代碼 (JMock 2.x 風格)

import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;

public class UserServiceTest {
    private final Mockery context = new Mockery();
    private final UserDAO mockDAO = context.mock(UserDAO.class);
    private final UserService service = new UserService();

    @Test
    public void testRegisterUser_shouldSaveUser() {
        // 設置期望:saveUser 被調用一次,參數 name="Alice"
        context.checking(new Expectations() {{
            oneOf(mockDAO).saveUser(with(any(User.class)));
        }});

        service.setUserDAO(mockDAO);
        service.registerUser("Alice");

        // 隱式驗證:context.assertIsSatisfied() 由 JUnit Runner 自動調用
    }

    @Test
    public void testFindUser_shouldReturnMockedUser() {
        final User expected = new User("Bob");

        context.checking(new Expectations() {{
            allowing(mockDAO).getUser(123L); will(returnValue(expected));
        }});

        service.setUserDAO(mockDAO);
        User actual = service.findUser(123L);

        assertSame(expected, actual);
    }
}

關鍵語法解析

語法含義
context.mock(Interface.class)創建接口的 Mock 對象
oneOf(mock).method(args)期望方法被調用恰好一次
allowing(mock).method(args)允許方法被調用任意次數(存根模式)
with(any(Class.class))參數匹配器:接受該類型任意實例
will(returnValue(obj))指定返回值
will(throwException(ex))指定拋出異常

最佳實踐原則

  1. 依賴接口而非實現:JDK 動態代理要求接口;若必須 Mock 類,需引入 CGLIB 並確保類非 final、方法非 final
  2. 只 Mock 直接依賴:不要 Mock 被測類內部 new 出來的對象,重構為構造器/Setter 注入
  3. 期望要精確:優先用 oneOf 驗證調用次數,避免過度寬鬆的 allowing 掩蓋 Bug
  4. 測試行為而非實現細節:關注「調用了什麼」、而非「怎麼調用的」
  5. 狀態驗證與行為驗證結合:必要時對被測類狀態斷言,而非全靠 Mock 驗證

總結

JMock 以聲明式期望替代手寫 Stub,讓隔離測試代碼可讀、可維護、聚焦業務邏輯。掌握「接口隔離、精確期望、行為驗證」三板斧,即可在 TDD 中暢行無阻,實現「先寫測試、後寫實現、依賴未就緒也能測」的工程理想。

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