所以我正在为我的数据库连接开发一个JUnit测试用例。因为我的表会自动增加它们的键,所以我试图在测试后重置自动增量,这样我就不会通过运行大量的测试来使用额外的值。
我正在使用SpringJUnit4ClassRunner的Spring事务,这会导致事务在完成时自动回滚,这很好。但是,当我尝试执行自动增量重置时会出现问题。
所以,这是我在课堂上的一个测试用例:
@Test
@Transactional
public void testStudentOperations(){
Student student = new Student();
setStudent1(student);
studentDao.insertStudent(student);
int studentId = student.getStudentId();
student = studentDao.getStudent(studentId);
assertNotNull(INSERT_FAIL, student);
assertEquals(INSERT_FAIL, student.getFirstName(), "First");
assertEquals(INSERT_FAIL, student.getLastName(), "Last");
assertEquals(INSERT_FAIL, student.getBirthDate(), LocalDate.of(1900, 1, 1));
assertEquals(INSERT_FAIL, student.getGender(), 'U');
assertEquals(INSERT_FAIL, student.getGrade(), 1);
setStudent2(student);
studentDao.updateStudent(student);
student = studentDao.getStudent(studentId);
assertNotNull(UPDATE_FAIL, student);
assertEquals(UPDATE_FAIL, student.getFirstName(), "First2");
assertEquals(UPDATE_FAIL, student.getLastName(), "Last2");
assertEquals(UPDATE_FAIL, student.getBirthDate(), LocalDate.of(1950, 1, 1));
assertEquals(UPDATE_FAIL, student.getGender(), 'M');
assertEquals(UPDATE_FAIL, student.getGrade(), 2);
studentDao.deleteStudent(student);
student = studentDao.getStudent(studentId);
assertNull(DELETE_FAIL, student);
}
创建实体,测试insert / update / delete / get操作,简单明了。这本身就很完美。测试全部运行,并且事务在最后完全回滚,因此我的数据库中没有虚拟数据。很干净。
但是,如果我添加这个@After方法,一切都变得棘手:
@Transactional (propagation=Propagation.REQUIRES_NEW)
@After
public void after(){
System.out.println("Running");
if(studentDao instanceof HibernateStudentDao){
System.out.println("Correct Dao");
((HibernateStudentDao) studentDao).resetAutoIncrement();
}
else{
throw new RuntimeException("Auto-Increment not reset");
}
}
因此,此方法是在测试用例之后运行并重置表的自动增量。自动递增重置SQL代码完美地工作(我将在末尾粘贴该代码)。问题是,当这个运行时,测试用例最终会部分提交。第一组值(“First”,“Last”等)将被提交到数据库,而第二组(“First2”,“Last2”等)将被回滚。
我不知道发生了什么事。我认为它与两个操作的混合有关,所以我尝试将事务传播设置为REQUIRE_NEW,但这似乎没有帮助。
有什么建议吗?这是我在其他功能齐全的测试案例中唯一存在的问题。
PS:这是自动增加代码:
public void resetAutoIncrement(){
Dialect dialect = ((SessionFactoryImplementor) sessionFactory).getDialect();
if(dialect instanceof MySQLDialect){
sessionFactory.getCurrentSession()
.createSQLQuery("alter table student auto_increment = 1")
.executeUpdate();
}
else{
throw new UnsupportedOperationException(
"Method is only compatible with MySQL database");
}
}
编辑:这是整个测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration ({"classpath:/test-context.xml"})
public class StudentDaoTest extends TestCase{
private static final String INSERT_FAIL = "Insert Failed";
private static final String UPDATE_FAIL = "Update Failed";
private static final String DELETE_FAIL = "Delete Failed";
//TODO document how this class has the Spring dependencies
@Autowired
private StudentDao studentDao;
public StudentDao getStudentDao() {
return studentDao;
}
public void setStudentDao(StudentDao studentDao) {
this.studentDao = studentDao;
}
@Transactional
@Test
public void testStudentOperations(){
Student student = new Student();
setStudent1(student);
studentDao.insertStudent(student);
int studentId = student.getStudentId();
student = studentDao.getStudent(studentId);
assertNotNull(INSERT_FAIL, student);
assertEquals(INSERT_FAIL, student.getFirstName(), "First");
assertEquals(INSERT_FAIL, student.getLastName(), "Last");
assertEquals(INSERT_FAIL, student.getBirthDate(), LocalDate.of(1900, 1, 1));
assertEquals(INSERT_FAIL, student.getGender(), 'U');
assertEquals(INSERT_FAIL, student.getGrade(), 1);
setStudent2(student);
studentDao.updateStudent(student);
student = studentDao.getStudent(studentId);
assertNotNull(UPDATE_FAIL, student);
assertEquals(UPDATE_FAIL, student.getFirstName(), "First2");
assertEquals(UPDATE_FAIL, student.getLastName(), "Last2");
assertEquals(UPDATE_FAIL, student.getBirthDate(), LocalDate.of(1950, 1, 1));
assertEquals(UPDATE_FAIL, student.getGender(), 'M');
assertEquals(UPDATE_FAIL, student.getGrade(), 2);
studentDao.deleteStudent(student);
student = studentDao.getStudent(studentId);
assertNull(DELETE_FAIL, student);
}
private void setStudent1(Student student){
student.setFirstName("First");
student.setLastName("Last");
student.setBirthDate(LocalDate.of(1900, 1, 1));
student.setGender('U');
student.setGrade(1);
}
private void setStudent2(Student student){
student.setFirstName("First2");
student.setLastName("Last2");
student.setBirthDate(LocalDate.of(1950, 1, 1));
student.setGender('M');
student.setGrade(2);
}
@Transactional
@Test
public void testListOperation(){
Student student = new Student();
setStudent1(student);
studentDao.insertStudent(student);
List<Student> students = studentDao.getAllStudents();
assertNotNull("Students list is null", students);
assertTrue("Students list less than 1", students.size() >= 1);
assertTrue("Students list doesn't contain student", students.contains(student));
}
@Transactional
@After
public void after(){
System.out.println("Running");
if(studentDao instanceof HibernateStudentDao){
System.out.println("Correct Dao");
((HibernateStudentDao) studentDao).resetAutoIncrement();
}
else{
throw new RuntimeException("Auto-Increment not reset");
}
}
编辑2:DAO课程:
public class HibernateStudentDao implements StudentDao {
/**
* The <tt>SessionFactory</tt> that this class uses
* for connecting to the database.
*/
private final SessionFactory sessionFactory;
/**
* Create this DAO with the mandatory <tt>SessionFactory</tt>
* that it requires. Passing null as this parameter will
* cause this class to not be able to function.
*
* @param sessionFactory the <tt>SessionFactory</tt> this class
* needs to create database sessions.
*/
public HibernateStudentDao(SessionFactory sessionFactory){
this.sessionFactory = sessionFactory;
}
/**
* Get the <tt>SessionFactory</tt> used by this class
* for database sessions.
*
* @return the <tt>SessionFactory used by this class.
* @throws NullPointerException if the <tt>SessionFactory</tt>
* was set to null.
*/
public SessionFactory getSessionFactory() {
return sessionFactory;
}
/**
* {@inheritDoc}
* @throws HibernateException if the database operation fails.
* @throws NullPointerException if the <tt>SessionFactory</tt>
* was set to null.
*/
@Override
public void insertStudent(Student student) {
sessionFactory.getCurrentSession().save(student);
}
/**
* {@inheritDoc}
* @throws HibernateException if the database operation fails.
* @throws NullPointerException if the <tt>SessionFactory</tt>
* was set to null.
*/
@Override
public void updateStudent(Student student) {
sessionFactory.getCurrentSession().update(student);
}
/**
* {@inheritDoc}
* @throws HibernateException if the database operation fails.
* @throws NullPointerException if the <tt>SessionFactory</tt>
* was set to null.
*/
@Override
public Student getStudent(int studentId) {
Session session = sessionFactory.getCurrentSession();
return (Student) session.createCriteria(Student.class)
.setFetchMode("courses", FetchMode.JOIN)
.add(Restrictions.naturalId().set("studentId", studentId))
.uniqueResult();
}
/**
* {@inheritDoc}
* @throws HibernateException if the database operation fails.
* @throws NullPointerException if the <tt>SessionFactory</tt>
* was set to null.
*/
@SuppressWarnings("unchecked") //Criteria.list() doesn't support generics
@Override
public List<Student> getAllStudents() {
Session session = sessionFactory.getCurrentSession();
return session.createCriteria(Student.class)
.list();
}
/**
* {@inheritDoc}
* @throws HibernateException if the database operation fails.
* @throws NullPointerException if the <tt>SessionFactory</tt>
* was set to null.
*/
@Override
public void deleteStudent(Student student) {
sessionFactory.getCurrentSession().delete(student);
}
/**
* Reset the auto-increment counter on the database table
* for the <tt>Student</tt> class. This will set the counter
* generating ids to the next highest number based on the
* records currently in the table. This is especially useful
* during testing operations.
* <p>
* <b>NOTE:</b> This operation is only compatible with a
* MySQL database, as it uses MySQL-specific syntax. Attempting
* to use it with a different database will cause an exception
* to be thrown.
*
* @throws HibernateException if the database operation fails.
* @throws NullPointerException if the <tt>SessionFactory</tt>
* was set to null.
* @throws UnsupportedOperationException if this operation is
* attempted with a database that's not MySQL.
*/
public void resetAutoIncrement(){
Dialect dialect = ((SessionFactoryImplementor) sessionFactory).getDialect();
if(dialect instanceof MySQLDialect){
sessionFactory.getCurrentSession()
.createSQLQuery("alter table student auto_increment = 1")
.executeUpdate();
}
else{
throw new UnsupportedOperationException(
"Method is only compatible with MySQL database");
}
}
/**
* Close the <tt>SessionFactory</tt> when this class's work
* is complete.
*
* @throws HibernateException if the database operation fails.
* @throws NullPointerException if the <tt>SessionFactory</tt>
* was set to null.
*/
public void closeSessionFactory(){
sessionFactory.close();
}
}
答案 0 :(得分:0)
这是因为你明确要求After方法使用新的交易
@Transactional (propagation=Propagation.REQUIRES_NEW) <----- that start an new transaction
@After
public void after(){...}
解决方案:
你可以:
Propagation.REQUIRED
代替REQUIRES_NEW
@Transactional
注释(并在Test类中放置一个)我会尝试解决方案3,如果这不起作用,我会使用第二个:
@RunWith(SpringJUnit4ClassRunner.class)
@Transactional
//has a own transaction for each test case that will rolled back
public MyTestClass {
@Before
public void before() {
//run within THE transaction
}
@After
public void after() {
//run within THE transaction
}
@Test
public void myTest() {
//run within THE same transaction
}
}
Spring Reference http://docs.spring.io/spring/docs/current/spring-framework-reference/html/integration-testing.html#testcontext-tx