2015-12-22 41 views
1

我使用TestNG 6.9.9構建迴歸測試環境。但遇到一個我在使用JUnit時從未遇到的問題。 在我看來,當完成每個測試用例時,如果測試方法在與他們所調用的相同的事務上下文中運行,則每個數據的更改將默認自動回滾。但似乎這不是事實,我不能發現我的代碼中是否有任何錯誤。請幫助我。在pom.xml中 特性,這表明框架版本Spring + TestNG不是事務性回滾

<properties> 
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 
    <springframework.version>4.2.4.RELEASE</springframework.version> 
    <hibernate.version>4.3.11.Final</hibernate.version> 
<testng.version>6.9.9</testng.version> 
</properties> 

顯然,他們都是跟上時代的。

我的測試類:

package com.noahwm.hkapp.api; 

import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.test.annotation.Rollback; 
import org.springframework.test.context.ContextConfiguration; 
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests; 
import org.springframework.test.context.transaction.TransactionConfiguration; 
import org.springframework.transaction.annotation.Propagation; 
import org.springframework.transaction.annotation.Transactional; 
import org.testng.Assert; 
import org.testng.annotations.Test; 

import com.noahwm.hkapp.api.db.dao.AppUserDao; 
import com.noahwm.hkapp.api.db.model.AppUser; 
import com.noahwm.hkapp.api.service.AppUserService; 

@ContextConfiguration(locations = { "classpath:applicationContext-test.xml" }) 
public class AppUserServiceTestNGTest extends AbstractTestNGSpringContextTests { 

    @Autowired 
    private AppUserService appUserService; 

    @Test 
    @Rollback 
    @Transactional 
    public void testApp() { 
    AppUser appUser = new AppUser(); 
    appUser.setAge(10); 
    appUser.setGender("F"); 
    appUser.setMobilePhone("13219201034"); 
    appUser.setName("HKAPP Test"); 
    appUserService.createUser(appUser); 
    String appUserId = appUser.getId(); 
    Assert.assertNotNull(appUserId); 
    } 
} 

創建了一個實體實例,不是要求的createUser()將它保存到數據庫中。根據我在JUnit中所做的,即使我沒有在測試方法的前面放置@Rollback註釋,數據也會自動回滾。

APPUSER的結構是:

package com.noahwm.hkapp.api.db.model; 

import javax.persistence.Column; 
import javax.persistence.Entity; 

@Entity(name = "APP_USERS") 
public class AppUser extends BaseDataModel { 

    @Column(name = "NAME") 
    private String name; 

    @Column(name = "GENDER") 
    private String gender; 

    @Column(name = "AGE") 
    private Integer age; 

    @Column(name = "MOBILE_PHONE") 
    private String mobilePhone; 

    public String getId() { 
    return id; 
    } 

    public void setId(String id) { 
    this.id = id; 
    } 

    public String getName() { 
    return name; 
    } 

    public void setName(String name) { 
    this.name = name; 
    } 

    public String getGender() { 
    return gender; 
    } 

    public void setGender(String gender) { 
    this.gender = gender; 
    } 

    public Integer getAge() { 
    return age; 
    } 

    public void setAge(Integer age) { 
    this.age = age; 
    } 

    public String getMobilePhone() { 
    return mobilePhone; 
    } 

    public void setMobilePhone(String mobilePhone) { 
    this.mobilePhone = mobilePhone; 
    } 

} 

BaseDataModel.java

package com.noahwm.hkapp.api.db.model; 

import javax.persistence.Column; 
import javax.persistence.GeneratedValue; 
import javax.persistence.Id; 
import javax.persistence.MappedSuperclass; 
import javax.persistence.Version; 

import org.hibernate.annotations.GenericGenerator; 

@MappedSuperclass 
public class BaseDataModel { 

    @Id 
    @GeneratedValue(generator = "uuid") 
    @GenericGenerator(name = "uuid", strategy = "org.hibernate.id.UUIDGenerator") 
    @Column(name = "ID", unique = true, length = 36, nullable = false) 
    protected String id; 

    @Version 
    @Column(name = "version") 
    protected Integer version; 

    public String getId() { 
    return id; 
    } 

    public void setId(String id) { 
    this.id = id; 
    } 

    public Integer getVersion() { 
    return version; 
    } 
} 

的ApplicationContext-的test.xml

<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" 
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:jee="http://www.springframework.org/schema/jee" 
    xmlns:context="http://www.springframework.org/schema/context" 
    xmlns:util="http://www.springframework.org/schema/util" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans-4.0.xsd 
      http://www.springframework.org/schema/context 
      http://www.springframework.org/schema/context/spring-context-4.0.xsd 
      http://www.springframework.org/schema/jee 
      http://www.springframework.org/schema/jee/spring-jee-4.0.xsd 
      http://www.springframework.org/schema/tx 
      http://www.springframework.org/schema/tx/spring-tx-4.0.xsd 
      http://www.springframework.org/schema/aop 
      http://www.springframework.org/schema/aop/spring-aop-4.0.xsd 
      http://www.springframework.org/schema/util 
      http://www.springframework.org/schema/util/spring-util-4.0.xsd"> 

    <bean 
     class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> 
     <property name="location"> 
      <value>classpath:jdbc.test.properties</value> 
     </property> 
    </bean> 

    <context:annotation-config /> 
    <context:component-scan base-package="com.noahwm.hkapp.api" /> 

    <aop:aspectj-autoproxy /> 

    <bean id="lobHandler" class="org.springframework.jdbc.support.lob.DefaultLobHandler" 
     lazy-init="true" /> 

    <bean id="dataSource" class="com.jolbox.bonecp.BoneCPDataSource" 
     destroy-method="close"> 
     <property name="driverClass" value="${jdbc.driver}" /> 
     <property name="jdbcUrl" value="${jdbc.url}" /> 
     <property name="username" value="${jdbc.username}" /> 
     <property name="password" value="${jdbc.password}" /> 
     <property name="maxConnectionsPerPartition" value="${jdbc.maxConnectionsPerPartition}" /> 
     <property name="minConnectionsPerPartition" value="${jdbc.minConnectionsPerPartition}" /> 
     <property name="partitionCount" value="${jdbc.partitionCount}" /> 
     <property name="acquireIncrement" value="${jdbc.acquireIncrement}" /> 
    </bean> 

    <bean id="sessionFactory" 
     class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> 
     <property name="dataSource" ref="dataSource" /> 
     <property name="packagesToScan"> 
      <list> 
       <value>com.noahwm.hkapp.api.db.model</value> 
      </list> 
     </property> 
     <property name="hibernateProperties"> 
      <props> 
       <prop key="hibernate.dialect">${hibernate.dialect}</prop> 
       <prop key="hibernate.show_sql">${hibernate.show_sql}</prop> 
       <prop key="hibernate.jdbc.batch_size">10</prop> 
       <prop key="hibernate.jdbc.fetch_size">30</prop> 
       <prop key="hibernate.default_batch_fetch_size">10</prop> 
      </props> 
     </property> 
    </bean> 

    <tx:annotation-driven transaction-manager="txManager" 
     proxy-target-class="true" /> 
    <bean id="txManager" 
     class="org.springframework.orm.hibernate4.HibernateTransactionManager"> 
     <property name="sessionFactory" ref="sessionFactory" /> 
    </bean> 
</beans> 

事務管理器被命名爲「txManager」。 AppUserService.java

package com.noahwm.hkapp.api.service; 

import java.util.List; 
import java.util.Map; 

import com.noahwm.hkapp.api.db.dao.AppUserDao; 
import com.noahwm.hkapp.api.db.model.AppUser; 

public interface AppUserService { 
    void createUser(AppUser user); 

} 

AppUserServiceImpl.java

package com.noahwm.hkapp.api.service.impl; 

import java.util.List; 
import java.util.Map; 

import org.hibernate.criterion.Order; 
import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.stereotype.Service; 
import org.springframework.transaction.annotation.Propagation; 
import org.springframework.transaction.annotation.Transactional; 
import org.springframework.util.StringUtils; 

import com.noahwm.hkapp.api.db.dao.AppUserDao; 
import com.noahwm.hkapp.api.db.model.AppUser; 
import com.noahwm.hkapp.api.service.AppUserService; 
import com.noahwm.hkapp.api.service.EntityService; 
import com.noahwm.hkapp.utils.SimpleSearchCriteria; 

@Service("AppUserService") 
@Transactional(propagation=Propagation.REQUIRED) 
public class AppUserServiceImpl extends EntityService implements AppUserService { 

    private static final Logger logger = LoggerFactory.getLogger(AppUserServiceImpl.class); 

    @Autowired 
    private AppUserDao dao; 
    @Override 
    public void createUser(AppUser user) { 
    logger.debug("Creating user with name {}", user.getName()); 
    dao.save(user); 
    } 
} 

AppUserDao.java

package com.noahwm.hkapp.api.db.dao; 

import org.springframework.stereotype.Repository; 
import org.springframework.transaction.annotation.Propagation; 
import org.springframework.transaction.annotation.Transactional; 

import com.noahwm.hkapp.api.db.model.AppUser; 

@Repository 
@Transactional(propagation=Propagation.REQUIRED) 
public class AppUserDao extends BaseDao<AppUser> { 
    public void testsRollBack(AppUser appUser) throws Exception{ 
    save(appUser); 
    } 
} 

BaseDao.java

package com.noahwm.hkapp.api.db.dao; 

import java.io.Serializable; 
import java.lang.reflect.ParameterizedType; 
import java.lang.reflect.Type; 
import java.util.Iterator; 
import java.util.List; 
import java.util.Map; 
import java.util.Map.Entry; 

import javax.annotation.Resource; 

import org.hibernate.Criteria; 
import org.hibernate.Query; 
import org.hibernate.Session; 
import org.hibernate.SessionFactory; 
import org.hibernate.criterion.Criterion; 
import org.hibernate.criterion.Order; 
import org.hibernate.criterion.Restrictions; 
import org.springframework.transaction.annotation.Transactional; 

import com.noahwm.hkapp.api.db.model.BaseDataModel; 
import com.noahwm.hkapp.utils.SimpleSearchCriteria; 

class BaseDao<T extends BaseDataModel> { 

    private Class<T> domainClass; 

    @Resource(name = "sessionFactory") 
    protected SessionFactory sessionFactory; 

    protected SessionFactory getSessionFactory() { 
    return sessionFactory; 
    } 

    public void setSessionFactory(SessionFactory sessionFactory) { 
    this.sessionFactory = sessionFactory; 
    } 

    @SuppressWarnings("unchecked") 
    public Class<T> getDomainClass() { 
    if (domainClass == null) { 
     Type type = this.getClass().getGenericSuperclass(); 
     ParameterizedType parameterizedType = (ParameterizedType) type; 
     domainClass = (Class<T>) parameterizedType.getActualTypeArguments()[0]; 
    } 
    return domainClass; 
    } 

    protected Session getCurrentSession() { 
    return getSessionFactory().getCurrentSession(); 
    } 

    public Criteria createCriteria() { 
    return getCurrentSession().createCriteria(getDomainClass()); 
    } 

    public void save(T o) { 
    getCurrentSession().save(o); 
    } 

    public void update(T o) { 
    getCurrentSession().update(o); 
    } 

    public void saveOrUpdate(T o) { 
    getCurrentSession().saveOrUpdate(o); 
    } 

    public Object merge(Object o) { 
    return getCurrentSession().merge(o); 
    } 

    public void delete(T o) { 
    getCurrentSession().delete(o); 
    } 

    public T deleteById(Serializable id) { 
    T o = findById(id); 
    getCurrentSession().delete(o); 
    return o; 
    } 

    public void evict(Object o) { 
    getCurrentSession().evict(o); 
    } 

    @SuppressWarnings("unchecked") 
    public List<T> findAll() { 
    return createCriteria().list(); 
    } 

    @SuppressWarnings("unchecked") 
    public T findById(Serializable o) { 
    List<T> results = createCriteria().add(Restrictions.idEq(o)).list(); 
    if (results.isEmpty()) { 
     return null; 
    } else { 
     return results.get(0); 
    } 
    } 

    @SuppressWarnings("unchecked") 
    public T load(Serializable o) { 
    return (T) getCurrentSession().load(getDomainClass(), o); 
    } 

    @SuppressWarnings("unchecked") 
    public List<T> findBy(Map<String, Object> propertyNameValues) { 
    return createCriteria().add(Restrictions.allEq(propertyNameValues)).list(); 
    } 

    @SuppressWarnings("unchecked") 
    public List<T> findBy(String propertyName, Object value) { 
    return createCriteria().add(Restrictions.eq(propertyName, value)).list(); 
    } 

    @SuppressWarnings("unchecked") 
    public List<T> find(SimpleSearchCriteria simpleSearchCriteria) { 
    Criteria criteria = createCriteria(); 
    Iterator<Criterion> criterions = simpleSearchCriteria.iterator(); 
    while(criterions.hasNext()) { 
     criteria.add(criterions.next()); 
    } 
    for(Order o : simpleSearchCriteria.getOrders()) { 
     criteria.addOrder(o); 
    } 
    if(simpleSearchCriteria.getFetchSize() != null) { 
     criteria.setFetchSize(simpleSearchCriteria.getFetchSize()); 
    } 
    if(simpleSearchCriteria.getFirstResult() != null) { 
     criteria.setFirstResult(simpleSearchCriteria.getFirstResult()); 
    } 
    if(simpleSearchCriteria.getMaxResults() != null) { 
     criteria.setMaxResults(simpleSearchCriteria.getMaxResults()); 
    } 
    if(simpleSearchCriteria.getTimeout() != null) { 
     criteria.setTimeout(simpleSearchCriteria.getTimeout()); 
    } 
    return criteria.list(); 
    } 

    public T findFirst(SimpleSearchCriteria simpleSearchCriteria) { 
    simpleSearchCriteria.setMaxResults(1); 
    List<T> results = find(simpleSearchCriteria); 
    if(results.isEmpty()) { 
     return null; 
    } else { 
     return results.get(0); 
    } 
    } 

    public Object callNamedQuery(String sql, Map<String, Object> parameter) { 
    Query query = getCurrentSession().createSQLQuery(sql); 
    for(Entry<String, Object> entry:parameter.entrySet()){ 
     query.setParameter(entry.getKey(), entry.getValue()); 
    } 
    return query.executeUpdate(); 
    } 
} 

這裏是DB初始化腳本:

CREATE TABLE "APP_USERS" (
"ID" VARCHAR(36), 
"NAME" VARCHAR(50), 
"GENDER" VARCHAR(1), 
"AGE" NUMERIC(3,0), 
"MOBILE_PHONE" VARCHAR(20), 
    VERSION INTEGER) 

如您所見,這是一個非常常見的Spring TestNG集成測試。但是不能使用自動回滾功能,這與我很接近。

+0

擴展正確的測試用例,'AbstractTransactionalTestNGSpringContextTests'而不是'AbstractTestNGSpringContextTests'。 –

+0

你是對的!非常感謝Deinum – Jomy

回答

2

感謝M. Deinum。 要解決我的問題,我只需用AbstractTransactionalTestNGSpringContextTests替換AbstractTestNGSpringContextTests類。

package com.noahwm.hkapp.api; 

import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.test.annotation.Rollback; 
import org.springframework.test.context.ContextConfiguration; 
import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests; 
import org.springframework.test.context.transaction.TransactionConfiguration; 
import org.springframework.transaction.annotation.Propagation; 
import org.springframework.transaction.annotation.Transactional; 
import org.testng.Assert; 
import org.testng.annotations.Test; 

import com.noahwm.hkapp.api.db.dao.AppUserDao; 
import com.noahwm.hkapp.api.db.model.AppUser; 
import com.noahwm.hkapp.api.service.AppUserService; 

@ContextConfiguration(locations = { "classpath:applicationContext-test.xml" }) 
public class AppUserServiceTestNGTest extends AbstractTransactionalTestNGSpringContextTests { 

    @Autowired 
    private AppUserService appUserService; 

    @Test 
    @Rollback 
    @Transactional 
    public void testApp() { 
    AppUser appUser = new AppUser(); 
    appUser.setAge(10); 
    appUser.setGender("F"); 
    appUser.setMobilePhone("13219201034"); 
    appUser.setName("HKAPP Test"); 
    appUserService.createUser(appUser); 
    String appUserId = appUser.getId(); 
    Assert.assertNotNull(appUserId); 
    } 
}