简体   繁体   English

在 spring testng 测试方法上禁用事务

[英]Disabling transaction on spring testng test method

I am using TestNG together with Spring and JPA.我将 TestNG 与 Spring 和 JPA 一起使用。 So far I am using to test database stuff my test class which extends AbstractTransactionalTestNGSpringContextTests .到目前为止,我正在使用扩展AbstractTransactionalTestNGSpringContextTests的测试类来测试数据库内容。 With @TransactionConfiguration(defaultRollback = true) everything works fine and I do not need to cary about cleanup.使用@TransactionConfiguration(defaultRollback = true)一切正常,我不需要担心清理。 Spring creates a default transaction on the beginning of each of my test method which is than rollbacked. Spring 在我的每个测试方法的开头创建一个默认事务,而不是回滚。 This is a very neat trick to solve famous "Transactional tests considered harmful" problem.这是解决著名的“交易测试被认为有害”问题的一个非常巧妙的技巧。

Unfortunately I need for one method in this class (one test) to not have this default transaction.不幸的是,我需要这个类中的一种方法(一个测试)没有这个默认事务。 This is because this test method simulates batch processing and I have multiple transactions inside it which are in production independent.这是因为此测试方法模拟批处理,并且我在其中有多个独立于生产的事务。 The only way I was able to simulate and solve problem is to configure those inner transactions with Propagation.REQUIRES_NEW but this I do not want to have in production code.我能够模拟和解决问题的唯一方法是使用Propagation.REQUIRES_NEW配置那些内部事务,但我不想在生产代码中使用它。 Is there some way to disable Spring transaction for my particular test method (so I do not need to use Propagation.REQUIRES_NEW but Propagation.REQUIRED in my service methods) ?有没有办法为我的特定测试方法禁用 Spring 事务(所以我不需要在我的服务方法中使用Propagation.REQUIRES_NEWPropagation.REQUIRED )?

I know you've already solved your problem, but for folks who will come here in the future...我知道你已经解决了你的问题,但是对于将来会来这里的人......

Unfortunately it seems that there is no way to disable existing transaction inside test that uses @Transactional annotation.不幸的是,似乎没有办法在使用@Transactional注释的测试中禁用现有事务。

IMHO Spring approach here is extremely inflexible.恕我直言,这里的 Spring 方法非常不灵活。 But there is a workaround for your problem.但是有一种解决方法可以解决您的问题。 It would be enough to encapsulate needed logic inside Spring TransactionTemplate class.在 Spring TransactionTemplate类中封装所需的逻辑就足够了。 This would ensure that code in your test case would be launched inside a new transaction.这将确保您的测试用例中的代码将在新事务中启动。


Personal advice : the best and the most flexible way from my point of view is to abandon from the very beginning @Transactional tests and setup database into known state before every test.个人建议:从我的角度来看,最好和最灵活的方法是从一开始就放弃@Transactional测试,并在每次测试之前将数据库设置为已知状态。 In this way, Spring will manage transactions exactly the same way as in production.这样,Spring 将以与生产环境完全相同的方式管理事务。
No quirks, no hacks, no manual transaction management.没有怪癖,没有黑客,没有手动交易管理。

I know that using @Transactional with a "rollback" policy around unit tests is a tempting idea, but it has too many pitfalls.我知道将@Transactional与围绕单元测试的“回滚”策略一起使用是一个诱人的想法,但它有太多的陷阱。 I recommend reading this article Spring Pitfalls: Transactional tests considered harmful .我推荐阅读这篇文章Spring Pitfalls: Transactional tests considered dangerous

Of course I don't complain here about @Transactional itself - because it greatly simplifies transaction management in the production code.当然,我在这里并不抱怨@Transactional 本身——因为它极大地简化了生产代码中的事务管理。

I have found that by executing the body of my test in separate thread prevent Spring from transaction.我发现通过在单独的线程中执行我的测试主体可以防止 Spring 进行事务。 So the workaround solution is something like:所以解决方法是这样的:

    @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; 
    } 
 } 
}

Spring Boot >=2.1.5.RELEASE has the following solution. Spring Boot >=2.1.5.RELEASE 有以下解决方案。

By default, data JPA tests are transactional and roll back at the end of each test.默认情况下,数据 JPA 测试是事务性的,并在每个测试结束时回滚。 See the relevant section in the Spring Framework Reference Documentation for more details.有关更多详细信息,请参阅 Spring Framework 参考文档中的相关部分。 If that is not what you want, you can disable transaction management for a test or for the whole class as follows:如果这不是您想要的,您可以为测试或整个班级禁用事务管理,如下所示:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringRunner.class)
@DataJpaTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public class ExampleNonTransactionalTests {

}

(s. Spring Doc ) (s. Spring Doc )

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM