TestNG multithreaded test with Spring @Transaction

2019-02-18 22:20发布

问题:

I am using TestNG to test persistence Spring modules (JPA+Hibernate) using AbstractTransactionalTestNGSpringContextTests as a base class. All important parts @Autowired, @TransactionConfiguration, @Transactional work just fine.

The problem comes when I am trying to run test in parallel threads with threadPoolSize=x, invocationCount=y TestNG annotation.

WARNING: Caught exception while allowing TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener@174202a] 
to process 'before' execution of test method [testCreate()] for test instance [DaoTest] java.lang.IllegalStateException:
Cannot start new transaction without ending existing transaction: Invoke endTransaction() before startNewTransaction().
at org.springframework.test.context.transaction.TransactionalTestExecutionListener.beforeTestMethod(TransactionalTestExecutionListener.java:123)
at org.springframework.test.context.TestContextManager.beforeTestMethod(TestContextManager.java:374)
at org.springframework.test.context.testng.AbstractTestNGSpringContextTests.springTestContextBeforeTestMethod(AbstractTestNGSpringContextTests.java:146)

... Has anybody faced this problem?

Here is the code:

@TransactionConfiguration(defaultRollback = false)
@ContextConfiguration(locations = { "/META-INF/app.xml" })
public class DaoTest extends AbstractTransactionalTestNGSpringContextTests {

@Autowired
private DaoMgr dm;

@Test(threadPoolSize=5, invocationCount=10)
public void testCreate() {
    ...
    dao.persist(o);
    ...
}
...

Update: It seems that AbstractTransactionalTestNGSpringContextTests maintains transaction only for main thread when all other test threads don't get their own transaction instance. The only way to solve that is to extend AbstractTestNGSpringContextTests and maintain transaction programmatically (instead of @Transactional annotation) per each method (i.e. with TransactionTemplate):

@Test(threadPoolSize=5, invocationCount=10)
public void testMethod() {
    new TransactionTemplate(txManager).execute(new TransactionCallbackWithoutResult() {
        @Override
        protected void doInTransactionWithoutResult(TransactionStatus status) {
            // transactional test logic goes here
        }
    }
}

回答1:

The transaction needs to be started in the same thread, here are more details:

Spring3/Hibernate3/TestNG: some tests give LazyInitializationException, some don't



回答2:

Don't you think this rather comes from org.springframework.test.context.TestContextManager not being thread safe (see https://jira.springsource.org/browse/SPR-5863)?

I faced the same issue when I wanted to launch my Transactional TestNG tests in parallel and you can see that Spring actually tries to bind the transaction to the right Thread.

However this fails randomly with this kind of errors. I extended AbstractTransactionalTestNGSpringContextTests with :

@Override
@BeforeMethod(alwaysRun = true)
protected synchronized void springTestContextBeforeTestMethod(
        Method testMethod) throws Exception {
    super.springTestContextBeforeTestMethod(testMethod);
}

@Override
@AfterMethod(alwaysRun = true)
protected synchronized void springTestContextAfterTestMethod(
        Method testMethod) throws Exception {
    super.springTestContextAfterTestMethod(testMethod);
}

(the key being the synchronized...)

and it is now working like a charm. I can't wait for Spring 3.2 though so that it can be completly parallized!