在 spring testng 测试方法上禁用事务
Disabling transaction on spring testng test method
我将 TestNG 与 Spring 和 JPA 一起使用。到目前为止,我正在使用扩展 AbstractTransactionalTestNGSpringContextTests
的测试 class 来测试数据库内容。使用 @TransactionConfiguration(defaultRollback = true)
一切正常,我不需要担心清理。 Spring 在我的每个测试方法的开头创建一个默认事务,然后回滚。这是解决著名的 "Transactional tests considered harmful" 问题的一个非常巧妙的技巧。
不幸的是,我需要此 class(一项测试)中的一种方法来取消此默认交易。这是因为此测试方法模拟批处理,并且我在其中有多个独立于生产的事务。我能够模拟和解决问题的唯一方法是使用 Propagation.REQUIRES_NEW
配置这些内部事务,但我不想在生产代码中使用。有什么方法可以为我的特定测试方法禁用 Spring 事务(所以我不需要在我的服务方法中使用 Propagation.REQUIRES_NEW
但 Propagation.REQUIRED
)?
我发现通过在单独的线程中执行我的测试主体可以防止 Spring 事务。所以解决方法是这样的:
@ContextConfiguration(classes = { test.SpringTestConfigurator.class })
@TransactionConfiguration(defaultRollback = false)
@Slf4j
@WebAppConfiguration
public class DBDataTest extends AbstractTransactionalTestNGSpringContextTests {
/**
* Variable to determine if some running thread has failed.
*/
private volatile Exception threadException = null;
@Test(enabled = true)
public void myTest() {
try {
this.threadException = null;
Runnable task = () -> {
myTestBody();
};
ExecutorService executor = Executors.newFixedThreadPool(1);
executor.submit(task);
executor.shutdown();
while (!executor.isTerminated()) {
if (this.threadException != null) {
throw this.threadException;
}
}
if (this.threadException != null) {
throw this.threadException;
}
} catch (Exception e) {
log.error("Test has failed.", e);
Assert.fail();
}
}
public void myTestBody() {
try {
// test body to do
}
catch (Exception e) {
this.threadException = e;
}
}
}
我知道你已经解决了你的问题,但是对于将来会来这里的人......
不幸的是,似乎没有办法在使用 @Transactional
注释的测试中禁用现有事务。
恕我直言 Spring 这里的方法非常不灵活。但是有一个解决方法可以解决您的问题。将所需的逻辑封装在 Spring TransactionTemplate
class 中就足够了。这将确保您的测试用例中的代码将在新事务中启动。
个人建议:从我的角度来看,最好和最灵活的方法是从一开始就放弃@Transactional
测试并将数据库设置为已知状态在 每次测试之前。这样,Spring 将以与生产中完全相同的方式管理事务。
没有怪癖,没有黑客,没有手动事务管理。
我知道使用 @Transactional
和围绕单元测试的 "rollback" 策略是一个诱人的想法,但它有太多的陷阱。我推荐阅读这篇文章 Spring Pitfalls: Transactional tests considered harmful。
当然我不会在这里抱怨@Transactional 本身——因为它极大地简化了生产代码中的事务管理。
我将 TestNG 与 Spring 和 JPA 一起使用。到目前为止,我正在使用扩展 AbstractTransactionalTestNGSpringContextTests
的测试 class 来测试数据库内容。使用 @TransactionConfiguration(defaultRollback = true)
一切正常,我不需要担心清理。 Spring 在我的每个测试方法的开头创建一个默认事务,然后回滚。这是解决著名的 "Transactional tests considered harmful" 问题的一个非常巧妙的技巧。
不幸的是,我需要此 class(一项测试)中的一种方法来取消此默认交易。这是因为此测试方法模拟批处理,并且我在其中有多个独立于生产的事务。我能够模拟和解决问题的唯一方法是使用 Propagation.REQUIRES_NEW
配置这些内部事务,但我不想在生产代码中使用。有什么方法可以为我的特定测试方法禁用 Spring 事务(所以我不需要在我的服务方法中使用 Propagation.REQUIRES_NEW
但 Propagation.REQUIRED
)?
我发现通过在单独的线程中执行我的测试主体可以防止 Spring 事务。所以解决方法是这样的:
@ContextConfiguration(classes = { test.SpringTestConfigurator.class })
@TransactionConfiguration(defaultRollback = false)
@Slf4j
@WebAppConfiguration
public class DBDataTest extends AbstractTransactionalTestNGSpringContextTests {
/**
* Variable to determine if some running thread has failed.
*/
private volatile Exception threadException = null;
@Test(enabled = true)
public void myTest() {
try {
this.threadException = null;
Runnable task = () -> {
myTestBody();
};
ExecutorService executor = Executors.newFixedThreadPool(1);
executor.submit(task);
executor.shutdown();
while (!executor.isTerminated()) {
if (this.threadException != null) {
throw this.threadException;
}
}
if (this.threadException != null) {
throw this.threadException;
}
} catch (Exception e) {
log.error("Test has failed.", e);
Assert.fail();
}
}
public void myTestBody() {
try {
// test body to do
}
catch (Exception e) {
this.threadException = e;
}
}
}
我知道你已经解决了你的问题,但是对于将来会来这里的人......
不幸的是,似乎没有办法在使用 @Transactional
注释的测试中禁用现有事务。
恕我直言 Spring 这里的方法非常不灵活。但是有一个解决方法可以解决您的问题。将所需的逻辑封装在 Spring TransactionTemplate
class 中就足够了。这将确保您的测试用例中的代码将在新事务中启动。
个人建议:从我的角度来看,最好和最灵活的方法是从一开始就放弃@Transactional
测试并将数据库设置为已知状态在 每次测试之前。这样,Spring 将以与生产中完全相同的方式管理事务。
没有怪癖,没有黑客,没有手动事务管理。
我知道使用 @Transactional
和围绕单元测试的 "rollback" 策略是一个诱人的想法,但它有太多的陷阱。我推荐阅读这篇文章 Spring Pitfalls: Transactional tests considered harmful。
当然我不会在这里抱怨@Transactional 本身——因为它极大地简化了生产代码中的事务管理。