【问题标题】:Integration testing concurrent idempotency in Grails 4 and Spock failing在 Grails 4 和 Spock 中集成测试并发幂等性失败
【发布时间】:2021-12-06 16:29:26
【问题描述】:

在集成测试中测试并发性时,我在事务和休眠会话方面遇到了一些问题。

基本上我们有一个更新一些不同的数据库条目的过程,包括一些会计条目。对于给定的 id,此过程应该是幂等的,包括从不同线程并发调用时。

我正在尝试更新一个有一段时间无法在 Grails 4.0.12 中运行的旧测试。该测试最后一次在 Grails 2.x 中运行。

为简单起见,下面的代码是通用的。

void testName() {
    given:
    Thing thing = Thing.findAll(someId)[0]
    int attemptsPerThing = 5
    
    when:
    def pool = Executors.newFixedThreadPool(attemptsPerThing)
    List<Future> futureList = []
    
    for (int i = 0; i < attemptsPerThing; i++) {
        Future future = pool.submit({
            try {
                serviceToBeTested.functionToBeTestedThatCommitsThingsToDB(thing)
            } catch (Exception _) {}
        })
        futureList.add(future)
    }
    
    for (Future future in futureList) {
        def futureResults = future.get()
        assert futureResults == null
    }
    
    pool.shutdown()
    assert pool.awaitTermination(60, TimeUnit.SECONDS)

    sessionFactory.currentSession.clear()
    
    then:
    // QUERY DATABASE AND ASSERT THAT THE CALL ONLY COMPLETED ONCE AND DATA IS AS EXPECTED
}

此测试失败。在最初的调查中,它看起来是失败的,因为在未来的闭包中没有任何东西以在测试中可见的方式提交。看起来好像该函数从未被调用过。

在询问该 try 块中捕获的异常时,我可以看到所有迭代实际上都失败了,并出现了与业务逻辑无关的异常。例外是:

org.hibernate.HibernateException: Illegal attempt to associate a collection with two open sessions. Collection : [com.kiind.coreservices.KiindUser.paymentInfoByCurrency#70]

上面的com.kiind.coreservices.KiindUser.paymentInfoByCurrency是一个域对象的集合,它们存在于数据库中,但只能在这个函数中读取。

所以我的问题是,是否有在 Grails 4 / Spock 中测试并发的最佳实践?在集成测试中测试并发性是不好的做法吗?

具体来说,有没有办法将测试的休眠会话附加到线程,以便它们都可以共享?或者让线程使用它们自己的会话,完成它们的事务,然后在then 期间使用数据库的当前状态更新测试中的会话?

感谢您的帮助,如果需要其他信息,请告诉我。

【问题讨论】:

    标签: hibernate grails concurrency integration-testing grails-orm


    【解决方案1】:

    我相信池中另一个线程查看一个线程的原子事务的唯一方法是关闭事务管理。您可以在 spock 集成测试中做到这一点,当然也可以在实际服务中做到这一点。但不建议这样做,交易可以避免这种情况。

    换句话说,@Guillaume 上面所说的。

    【讨论】:

      【解决方案2】:

      您正在从 Hibernate Session 的主线程中加载一个对象:

      Thing thing = Thing.findAll(someId)[0]
      

      然后你在另一个线程中使用同一个对象:

      serviceToBeTested.functionToBeTestedThatCommitsThingsToDB(thing)
      

      Hibernate 会话和与该会话关联的对象完全不是线程安全的,因此您不能这样做。让每个线程管理自己的会话并且永远不会在线程之间传递对象的安全方式。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-02-21
        • 2011-07-03
        • 2012-05-06
        相关资源
        最近更新 更多