【问题标题】:SpringBoot / RabbitMq integration tests not working anymore on different environmentSpringBoot / RabbitMq 集成测试不再适用于不同的环境
【发布时间】:2017-11-21 04:52:49
【问题描述】:

我有一个测试套件在 Windows Jenkins slave 和本地(在 Windows 上)运行良好,我们现在正在将 Jenkins slave 迁移到 Docker Linux 映像上。

正常的构建在新的 slave 上运行不正常,我们看到集成测试失败,尤其是那些期望从 RabbitMq 接收一些消息的测试。 Java 和 Maven 版本在新的 Linux 从属服务器上可能略有不同,但我不明白为什么在新环境中不再运行几个月的东西。

我发现调试起来相当困难,而且我不知道从哪里开始,因为它正在我的机器上运行..

是否有人已经面临这种行为并可以提出建议?

【问题讨论】:

    标签: java spring-boot rabbitmq spring-rabbit


    【解决方案1】:

    这就是我解决问题的方法:我不想在测试中添加太多 Thread.sleep() 以允许我在 RabbitMq UI 上实时检查是否可以看到消息通过并被消耗...所以我添加了更多日志。

    我很快发现该消息已被侦听器使用,即使我的测试失败了。我的测试试图检查当处理过程中出现故障时,消息被发送到死信队列。所以我在死信队列上有一个监听器。以下是我在日志中可以看到的内容的快速摘要:

    18:51:05 2017-06-16 18:51:04,907 [main] DEBUG KickOffEventListenerIT    - sent message !!
    ...
    18:51:05 2017-06-16 18:51:04,970 [SimpleAsyncTaskExecutor-1] INFO  DeadLetterQueueListener   - received a message on deadletter queue 
    

    但我的测试失败了:

        deadLetterQueueListener.getLatch().await(1000, TimeUnit.MILLISECONDS);
        assertThat(deadLetterQueueListener.getReceivedMessages()).isNotEmpty();
    

    正在给予:

    java.lang.AssertionError: Expecting actual not to be empty
    

    我做了两件事:

    1. 在我的构建过程中确保队列/交换是唯一的

    我怀疑队列中可能有多个消费者,因为其他一些作业并行运行并连接在相同的交换/队列上。所以我想确保每次运行的名称都是唯一的。

    按照Look up hostname from Maven,这就是我所做的:

    • 配置 Maven 构建以访问主机名并稍后通过此 Maven 插件配置将其注入(以下配置应包装在 plugin XML 元素中,但由于某种原因,stackoverflow 不接受它):

      <groupId>org.codehaus.gmaven</groupId>
      <artifactId>gmaven-plugin</artifactId>
      <version>1.5</version>
      <executions>
          <execution>
              <phase>generate-resources</phase>
              <goals>
                  <goal>execute</goal>
              </goals>
              <configuration>
                  <providerSelection>2.0</providerSelection>
                  <source>
                      project.properties["hostname"] = InetAddress.getLocalHost().getHostName()
                  </source>
              </configuration>
          </execution>
      </executions>
      

    并像这样配置我的应用程序:

    myApp.messaging:
                dead.letter:
                        exchange:
                            name: myApp.dead.letter.exchange-@hostname@
                        queue:
                            name: myApp.dead.letter.queue-@hostname@
    

    这样,队列/交换会像这样创建:

    DEBUG RabbitAdmin - declaring Exchange 'myApp.dead.letter.exchange-d11525d215a8'
    DEBUG RabbitAdmin - declaring Queue 'myApp.dead.letter.queue-d11525d215a8'
    

    因为对于每个作业,Docker 都会旋转一个新的“机器”,主机名每次都不同。但尽管如此,它仍然无法正常工作。 我在测试和监听器中添加了更多日志,因为我有一个疑问:可以解释这个问题的唯一原因是我没有断言我应该反对监听器。我可以在日志中看到:

    • 在我的测试中:

      DEBUG KickOffEventListenerIT    - sent message !!
      DEBUG KickOffEventListenerIT    - waiting infos from deadLetterQueueListener myApp.remote.service.mocks.DeadLetterQueueListener@37093884
      DEBUG KickOffEventListenerIT    - waiting infos from latch java.util.concurrent.CountDownLatch@7e2e2d7d[Count = 1]
      
    • 在监听器中:

      DEBUG DeadLetterQueueListener   - processing infos from deadLetterQueueListener myApp.remote.service.mocks.DeadLetterQueueListener@2142ab69
      DEBUG DeadLetterQueueListener   - processing infos from latchjava.util.concurrent.CountDownLatch@79f8793b[Count = 1]
      INFO  DeadLetterQueueListener   - deadletter queue received message size : 1
      

    --> 我可以非常清楚地看到,我所断言的听众的引用和获取消息的听众的引用是不同的!显然,这就是我的测试失败的原因。

    --> 在我的测试套件的某个地方,另一个侦听器仍然处于活动状态并正在使用该消息。

    1. 清楚地确定收到消息的消费者

    为此,我在创建时清楚地记录了侦听器的 ID:

    @Component
    @Slf4j
    public class DeadLetterQueueListener {
    
        @PostConstruct
        public void logReferenceId(){
            log.debug("just built deadLetterQueueListener : "+this);
        }
    
        ...
    }
    

    然后通过查看日志中的对象引用就很明显了:

    19:24:21 Running myApp.service.impl.RequestCodeGeneratorServiceImplIT
    19:24:23 2017-06-16 19:24:22,907 [main] DEBUG DeadLetterQueueListener   - just built deadLetterQueueListener :  myApp.remote.service.mocks.DeadLetterQueueListener@58984698
    19:24:28 Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 6.058 sec - in myApp.service.impl.RequestCodeGeneratorServiceImplIT
    
    
    19:24:29 Running myApp.messaging.incoming.KickOffEventListenerIT
    19:24:30 2017-06-16 19:24:29,913 [main] DEBUG DeadLetterQueueListener   - just built deadLetterQueueListener : myApp.remote.service.mocks.DeadLetterQueueListener@32096336
    19:24:31 2017-06-16 19:24:31,187 [main] DEBUG KickOffEventListenerIT    - waiting infos from deadLetterQueueListener myApp.remote.service.mocks.DeadLetterQueueListener@32096336
    19:24:31 2017-06-16 19:24:31,250 [SimpleAsyncTaskExecutor-1] DEBUG DeadLetterQueueListener   - processing infos from deadLetterQueueListener myApp.remote.service.mocks.DeadLetterQueueListener@58984698
    

    我确认之前测试中的“僵尸侦听器”仍然存在并正在使用消息,而不是我在测试中创建的侦听器。我检查了第一个测试 (RequestCodeGeneratorServiceImplIT),并注意到 它没有 @DirtiesContext 注释。我添加它以确保所有内容都已清理,现在我的测试套件通过了!

    尽管我找到了解决问题的方法,但我仍然不清楚一些事情: - java/maven 版本的微小差异如何产生这种影响? - 为什么上一个测试中的侦听器仍然启动并运行,尽管测试正确完成? - 我真的必须将@DirtiesContext 放在我所有加载Spring 上下文的集成测试中吗?

    如果有人对此有一些答案,我会很感兴趣。

    【讨论】:

    • “我真的必须在所有加载 Spring 上下文的集成测试中添加 @DirtiesContext 吗?”你有没有发现?我想我有同样的问题。即使使用@DirtiesContext 注释测试,RabbitListeners 的一些旧实例仍然存在,然后在以后的测试中接收到不适合它们的消息。
    • 不,我还没有真正发现.. 但与此同时,响应中的步骤应该可以帮助您确定在哪个测试中创建了僵尸侦听器 - 您可能会错过一个 DirtiesContext测试!
    • 是的,这有帮助。就我而言,DirtiesContext 似乎并没有真正取消注册监听器。我目前正在评估在 IntegrationTests 中默认使用模拟的 Rabbit 是否更好,并且只在需要真正 Rabbit 的 IntegrationTests 中使用真实的。这可以通过弹簧轮廓来决定。有了这个,就可以避免将DirtiesContext 添加到一堆不相关的测试中。
    猜你喜欢
    • 1970-01-01
    • 2018-03-22
    • 2016-01-06
    • 2012-03-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多