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

Spring 2.5 TestContext 測試框架實戰:集成測試的正確姿勢

     編輯:Java 相關知識

Spring 2.5 引入基於註解的 TestContext 框架,徹底改變 Spring 集成測試寫法。支援 JUnit 3.8/4.4、TestNG,解決傳統 JUnit 測試的四大痛點:容器重複啟動、Bean 手動獲取、資料庫現場汙染、事務環境驗證困難。

傳統 JUnit 測試 Spring 的四大缺陷

問題原因後果
容器多次初始化每個測試方法建立實例、調用 setUp()啟動慢、資源浪費
硬編碼獲取 Beanctx.getBean("xxx") + 強制轉型重構易碎、類型不安全
資料庫現場破壞測試數據殘留、主鍵衝突測試不可重複執行
同事務驗證困難測試方法與業務代碼不同事務無法即時查詢驗證 DB 狀態

TestContext 核心架構

TestContext 核心類圖

  • TestContext:封裝測試上下文 (ApplicationContext、測試實例、配置位置)
  • TestContextManager:框架入口,管理 TestContext,發布事件給監聽器
  • TestExecutionListener:事件監聽器介面,內建實現:
    • DependencyInjectionTestExecutionListener:處理 @Autowired 注入
    • DirtiesContextTestExecutionListener:處理 @DirtiesContext 重載容器
    • TransactionalTestExecutionListener:處理事務註解、回滾

實戰:測試 UserService.handleUserLogin()

被測服務類

public class UserService {
    private UserDao userDao;
    private LoginLogDao loginLogDao;
    
    public void handleUserLogin(User user) {
        user.setCredits(5 + user.getCredits());
        LoginLog loginLog = new LoginLog();
        loginLog.setUserId(user.getUserId());
        loginLog.setIp(user.getLastIp());
        loginLog.setLoginTime(user.getLastVisit());
        userDao.updateLoginInfo(user);
        loginLogDao.insertLoginLog(loginLog);
    }
    // getters/setters
}

Spring 配置 (applicationContext.xml)

<beans ... xmlns:tx="..." xmlns:aop="..." xmlns:context="...">
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" .../>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" p:dataSource-ref="dataSource"/>
    <bean id="userDao" class="com.baobaotao.dao.UserDao" p:jdbcTemplate-ref="jdbcTemplate"/>
    <bean id="loginLogDao" class="com.baobaotao.dao.LoginLogDao" p:jdbcTemplate-ref="jdbcTemplate"/>
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSource"/>
    <bean id="userService" class="com.baobaotao.service.UserService" p:userDao-ref="userDao" p:loginLogDao-ref="loginLogDao"/>
    
    <aop:config>
        <aop:pointcut id="serviceMethods" expression="within(com.baobaotao.service..*)"/>
        <aop:advisor pointcut-ref="serviceMethods" advice-ref="txAdvice"/>
    </aop:config>
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes><tx:method name="*"/></tx:attributes>
    </tx:advice>
</beans>

測試類實現 (繼承 AbstractTransactionalJUnit4SpringContextTests)

@ContextConfiguration  // 預設載入 classpath:/com/baobaotao/service/TestUserService-context.xml
public class TestUserService extends AbstractTransactionalJUnit4SpringContextTests {

    @Autowired
    private UserService userService;
    
    @Autowired
    private UserDao userDao;
    
    private int userId;
    
    @Before  // 每個測試方法前執行,同事務環境
    public void prepareTestData() {
        // 使用 simpleJdbcTemplate (父類提供) 插入測試數據
        final String sql = "insert into t_user(user_name,password) values('tom','1234')";
        simpleJdbcTemplate.update(sql);
        
        // 獲取自增主鍵
        KeyHolder keyHolder = new GeneratedKeyHolder();
        simpleJdbcTemplate.getJdbcOperations().update(
            new PreparedStatementCreator() {
                public PreparedStatement createPreparedStatement(Connection conn) throws SQLException {
                    return conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
                }
            }, keyHolder);
        userId = keyHolder.getKey().intValue();
    }
    
    @Test
    public void handleUserLogin() {
        User user = userDao.getUserById(userId);
        user.setLastIp("127.0.0.1");
        Date now = new Date();
        user.setLastVisit(now.getTime());
        userService.handleUserLogin(user);
        
        // === 業務結果驗證 ===
        User newUser = userDao.getUserById(userId);
        Assert.assertEquals(5, newUser.getCredits());  // 積分增加 5
        Assert.assertEquals(now.getTime(), newUser.getLastVisit());
        Assert.assertEquals("127.0.0.1", newUser.getLastIp());
        
        // 驗證登錄日誌
        String sql = "select count(1) from t_login_log where user_id=? and login_datetime=? and ip=?";
        int logCount = simpleJdbcTemplate.queryForInt(sql, user.getUserId(), user.getLastVisit(), user.getLastIp());
        Assert.assertEquals(1, logCount);
    }
}

測試專用配置 (TestUserService-context.xml)

<beans ...>
    <import resource="classpath:/applicationContext.xml"/>
</beans>

關鍵註解與功能詳解

註解作用屬於
@ContextConfiguration指定 Spring 配置位置,支援 locations、inheritLocations類級
@Autowired測試類欄位/Setter 自動注入 Bean欄位/方法
@Before / @AfterJUnit 4.x 生命週期,同測試方法事務方法
@BeforeTransaction / @AfterTransaction事務開始前/結束後執行,適合準備/驗證全局數據方法
@Transactional方法級事務控制,預設回滾類/方法
@NotTransactional禁用事務 (預設父類啟用事務)方法
@Rollback(false)提交事務而非回滾方法
@TransactionConfiguration自定義事務管理器、預設回滾策略類級
@DirtiesContext標記測試汙染容器,執行後重載 ApplicationContext類/方法

父類提供的便利工具

  • simpleJdbcTemplate:自動配置的 SimpleJdbcTemplate,直接操作 DB
  • applicationContext:ApplicationContext 引用,可獲取任意 Bean
  • countRowsInTable("table"):快速統計表行數
  • deleteFromTables("t1", "t2"):批量清表
  • executeSqlScript("path.sql", true):執行 SQL 腳本 (一行一條)

抽象基類體系

@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class})
public class AbstractJUnit4SpringContextTests implements ApplicationContextAware { ... }

@TestExecutionListeners({TransactionalTestExecutionListener.class})
@Transactional
public class AbstractTransactionalJUnit4SpringContextTests extends AbstractJUnit4SpringContextTests { ... }

關鍵點:@RunWith(SpringJUnit4ClassRunner.class) 將 TestContext 無縫整合進 JUnit 4.x 運行器;測試類不需在 Spring 配置中定義,也不需 <context:annotation-config/>,監聽器由框架自動註冊。

支援矩陣

  • JUnit 3.8:org.springframework.test.context.junit38
  • JUnit 4.4+:org.springframework.test.context.junit4
  • TestNG:org.springframework.test.context.testng

💡 核心價值總結

  • 容器快取:同一測試類/父類共享 ApplicationContext,避免重複啟動
  • 聲明式事務:測試方法預設事務回滾,資料庫「踏雪無痕」
  • 同事務驗證:測試代碼與業務代碼同事務,可即時查詢驗證 DB 變更
  • 依賴注入:測試類直接 @Autowired,告別 getBean() 強轉
  • 靈活生命週期:@Before/@BeforeTransaction/@AfterTransaction 精準控制數據準備/驗證時機

TestContext 框架讓 Spring 集成測試從「痛苦的必要惡」變為「優雅的工程實踐」,是每個 Spring 開發者必須掌握的核心技能。

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