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

Spring 2.5 + 反射:極簡 Mock 測試實戰

     編輯:Java 相關知識

痛點:傳統 Mock 測試的樣板代碼

早期 JUnit 3 + Spring 舊版 TestContext 寫法:

public class HelloworldTest extends AbstractSingleSpringContextTests {
    private Foo foo = createMock(Foo.class);
    private Bar bar = createMock(Bar.class);
    private Helloworld helloworld;

    @Before
    public void before() {
        reset(foo, bar);
        helloworld = new Helloworld(foo, bar);
    }
    @After
    public void after() { verify(foo, bar); }
    @Test
    public void shouldSayHello() {
        replay(foo, bar);
        helloworld.sayHello();
    }
}

缺點顯而易見:

  1. 大量手工創建/重置/驗證 Mock 代碼,與業務測試無關。
  2. 每個測試類都要顯式列出用到的 Mock 物件。
  3. 強制繼承 Spring 測試基類,耦合度高。

解決方案一:Spring IOC 容器管理 Mock 物件

1. 用 FactoryBean 定義 Mock Bean

<bean id="mockFoo" class="org.easymock.EasyMock" factory-method="createMock">
    <constructor-arg index="0" value="com.example.Foo"/>
</bean>

2. 測試專用配置檔

利用 @ContextConfiguration("test-context.xml") 載入測試專屬上下文,僅定義 Mock 及被測物件:

<bean id="helloworld" class="com.example.Helloworld" autowire="byType"/>

3. 測試類 POJO 化

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("test-context.xml")
public class HelloworldTest {
    @Autowired private Foo foo;
    @Autowired private Bar bar;
    @Autowired private Helloworld helloworld;
    // ... 測試方法
}

優點:零樣板代碼創建 Mock,依賴注入自動完成,測試類成純 POJO。

解決方案二:反射實現通用 reset/replay/verifyAll

觀察發現:測試類中所有 @Autowired 欄位若為 Mock 物件(即動態代理或 CGLIB 增強類),則需統一驗證。編寫工具類:

public final class MockTestHelper {
    public static void resetAll(Object testObject) {
        reset(getDeclaredMockedFields(testObject));
    }
    public static void replayAll(Object testObject) {
        replay(getDeclaredMockedFields(testObject));
    }
    public static void verifyAll(Object testObject) {
        verify(getDeclaredMockedFields(testObject));
    }

    private static Object[] getDeclaredMockedFields(Object testObject) {
        List mocks = new ArrayList<>();
        for (Field field : testObject.getClass().getDeclaredFields()) {
            if (field.isAnnotationPresent(Autowired.class)) {
                boolean accessible = field.isAccessible();
                try {
                    field.setAccessible(true);
                    Object value = field.get(testObject);
                    if (isClassProxy(value.getClass())) mocks.add(value);
                } catch (IllegalAccessException e) { e.printStackTrace(); }
                finally { field.setAccessible(accessible); }
            }
        }
        return mocks.toArray();
    }

    private static boolean isClassProxy(Class clazz) {
        String name = clazz.getName();
        return name.contains("$Proxy") || name.contains("$$EnhancerByCGLIB$$");
    }
}

重構後的測試類

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("test-context.xml")
public class HelloworldTest {
    @Autowired private Foo foo;
    @Autowired private Bar bar;
    @Autowired private Helloworld helloworld;

    @Before public void before() { MockTestHelper.resetAll(this); }
    @After public void after() { MockTestHelper.verifyAll(this); }

    @Test
    public void shouldSayHello() {
        MockTestHelper.replayAll(this);
        helloworld.sayHello();
        // 斷言驗證
    }
}

新增 Mock 欄位時,零修改驗證邏輯,Helper 自動掃描處理。

延伸推薦:Mockito

EasyMock 需 replay/verify 切換狀態,語法較繁。推薦 Mockito:

  • 語義更清晰:when(mock.foo()).thenReturn(bar)。
  • 無需 replay/verify,預設驗證調用次數。
  • 支援 Spy、Stub 等更豐富的測試雙工具。

總結

Spring 2.5 的 Annotation 驅動測試框架 + 反射掃描,徹底消除 Mock 測試的樣板代碼,讓測試類回歸業務意圖表達。配合 Mockito 更能提升可讀性與維護性。

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