Spring Batch Job的集成测试失败

时间:2014-04-15 04:27:14

标签: java spring integration-testing spring-batch amazon-ses

好的,我知道这听起来很简单,但我真的很难理解为什么会这样。

因此,我使用Spring Batch生成要使用亚马逊的简单电子邮件服务发送的电子邮件。在我的CustomItemProcessor内,我正在使用@Autowired正常连接我的AmazonEmailService服务。 AmazonEmailService类实现了我的EmailSender接口。

AmazonEmailService有一个@Autowired AmazonSimpleEmailServiceClient,用于实际调用Amazon Simple Email Service来执行操作。

我的AmazonSimpleEmailServiceClient bean在我的root-servlet.xml中定义:

<bean id="amazonSimpleEmailServiceClient"
    class="com.amazonaws.services.simpleemail.AmazonSimpleEmailServiceClient">
    <constructor-arg ref="basicAWSCredentials" />
</bean>

<bean id="basicAWSCredentials" class="com.amazonaws.auth.BasicAWSCredentials">
    <constructor-arg index="0" value="${aws.accessKey}"/>
    <constructor-arg index="1" value="${aws.secretKey}" />
</bean>

这一切都很好。

我的问题是,当我运行Spring Batch Job集成测试时。他们在尝试发送电子邮件时挂起。我的日志记录显示执行只是在调用amazonSimpleEmailServiceClient.send(emailRequest)时停止,并且没有继续。

让我绝对陷入困境的是,如果我在为Spring Batch Jobs运行集成测试之前运行AmazonEmailService的单元测试,那么一切都会成功完成。我想知道为什么会这样。值得一提的是,实际发送电子邮件的批处理作业在单个线程上运行TaskExecutor

Spring Batch作业的集成测试应该验证作业是否成功为其收到的每个输入生成电子邮件,并且可以使用Amazon SES发送这些电子邮件。它还测试它是否正确地读取和写入已设置的队列的对象,但这与我的问题无关。 AmazonEmailService的单元测试只发送3封电子邮件到亚马逊电子邮件模拟器。

我会在下面发布一个简略的类图,这样你就可以看到事情是如何结合在一起的。

Class Diagram

  • EmailService界面是我自己的界面
  • AmazonEmailService是我自己的服务。该服务实际上使用AmazonSimpleEmailServiceClient执行电子邮件的发送,这是亚马逊给我的对象。
  • CustomItemProcessor是我自己的对象,它实现了ItemProcessor接口。这是Spring Batch用于实际处理批处理作业中的项目的内容。电子邮件应该由这个类生成和发送。
  • AmazonEmailServiceTest只是一个测试能力的单元测试 实际发送电子邮件的AmazonEmailService。

在考虑为什么我遇到这个愚蠢的问题时你可能会想到的事情:

  1. 我已正确配置我的单元/集成测试以在Spring应用程序上下文中运行。
  2. AmazonEmailServiceTest自行成功运行。
  3. 如果我恢复使用简单的JavaMail电子邮件发件人,则Spring Batch集成测试会成功运行。
  4. 使用XML配置为我的所有其他类正确配置和定义了我的Spring Batch作业,应用程序上下文和bean。
  5. 我的亚马逊凭据有效且工作正常。
  6. 正确自动配置类。
  7. 在尝试独立运行Spring Batch测试或使用AmazonEmailServiceTest
  8. 的任何阶段都不会抛出任何异常
  9. 同时运行测试时,同一个AmazonEmailService实例将自动连接到两个测试中(即两个测试的内存地址相同等)
  10. 我无法检查实际单元测试中使用的凭据。
  11. 我已在XML配置中正确配置了PropertyPlaceholderConfigurer。
  12. amazonSimpleEmailServiceClient.send(emailRequest)的调用将暂停,直到我手动停止单元测试。
  13. 在自动装配的应用程序上下文中可以发现相关的bean。
  14. 如果您需要更多信息,例如课程或配置,请不要犹豫。我真的坐在电脑前等待回复。

    AmazonEmailService:

    @Service
    public class AmazonEmailService implements EmailService {
    
        @Autowired
        private AmazonSimpleEmailServiceClient amazonSimpleEmailServiceClient;
    
        @Override
        public String sendEmail(Email email){
            //build request using Builder pattern//
            return amazonSimpleEmailServiceClient.sendEmail(emailRequest);
        }
    }
    

    CustomItemProcessor:

    public class CustomProcessQueueItemProcessor implements
        ItemProcessor<Foo, Bar> {
    
        @Autowired
        private EmailService amazonEmailService;
    
        @Override
        public Bar process(Foo foo) throws Exception {
            //generate email from Foo object//
            String result = amazonEmailService.sendEmail(email);
            //create Bar object from result//
            return bar;
        }
    }
    

    AmazonEmailServiceTest:

    public class AmazonEmailServiceTest extends SpringTest{
    
    @Autowired
    private EmailService amazonEmailService;
    
    @Test
    public void testSendEmailSuccess() {
        Email successEmail = MockObjectFactory.setTestSuccessEmail();
        String emailResultId = amazonEmailService.sendEmail(successEmail);
        assertNotNull("The returned emailResultId was null", emailResultId);
    }
    }
    

    SpringTest类是我将单元测试配置为在Spring应用程序上下文中运行的地方。我的MockObjectFactory就是它的名字所暗示的,一个包含生成测试对象的静态方法的类。

    批量Servlet:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:batch="http://www.springframework.org/schema/batch"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
            http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-2.2.xsd">
    
        <import resource="jobs/fill-queue-job.xml" />
        <import resource="jobs/process-queue-job.xml" />
    
        <batch:job-repository id="jobRepository"
            data-source="dataSource" transaction-manager="transactionManager" />
    
        <bean id="jobLauncher"
            class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
            <property name="jobRepository" ref="jobRepository" />
            <property name="taskExecutor" ref="defaultTaskExecutor"></property>
        </bean>
    
        <bean id="jobRegistry"
            class="org.springframework.batch.core.configuration.support.MapJobRegistry" />
    
        <bean id="jobRegistryBeanPostProcessor"
            class="org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor">
            <property name="jobRegistry" ref="jobRegistry" />
        </bean>
    
        <bean id="jobLoader"
            class="org.springframework.batch.core.configuration.support.DefaultJobLoader">
            <property name="jobRegistry" ref="jobRegistry" />
        </bean>
    
        <bean id="jobExplorer"
            class="org.springframework.batch.core.explore.support.JobExplorerFactoryBean">
            <property name="dataSource" ref="dataSource" />
        </bean>
    
        <bean id="jobOperator"
            class="org.springframework.batch.core.launch.support.SimpleJobOperator">
            <property name="jobLauncher" ref="jobLauncher" />
            <property name="jobRepository" ref="jobRepository" />
            <property name="jobRegistry" ref="jobRegistry" />
            <property name="jobExplorer" ref="jobExplorer" />
        </bean>
    
        <bean id="domainObjectIdQueue" class="java.util.concurrent.ConcurrentLinkedQueue" />
    
        <bean id="mainQueue" class="java.util.concurrent.ConcurrentLinkedQueue" />
    
        <bean id="notificationQueue" class="java.util.concurrent.ConcurrentLinkedQueue" />
    
        <bean id="fillQueueItemReader"
            class="au.com.mail.batch.itemreaders.CustomServiceItemReader"
            scope="step">
            <constructor-arg ref="emailTaskServiceImpl" />
        </bean>
    
        <bean id="fillQueueItemProcessor"
            class="au.com.mail.batch.itemprocessors.CustomFillQueueItemProcessor"
            scope="step" />
    
        <bean id="fillQueueCompositeItemWriter"
            class="org.springframework.batch.item.support.CompositeItemWriter">
            <property name="delegates">
                <list>
                    <bean id="fillQueueItemWriter"
                        class="au.com.mail.batch.itemwriters.CustomQueueItemWriter"
                        scope="step" />
                    <bean id="emailTaskItemWriter"
                        class="au.com.mail.batch.itemwriters.CustomServiceItemWriter"
                        scope="step">
                        <constructor-arg ref="emailTaskServiceImpl" />
                    </bean>
                </list>
            </property>
        </bean>
    
        <bean id="processQueueItemReader"
            class="au.com.mail.batch.itemreaders.CustomQueueItemReader"
            scope="step">
            <constructor-arg>
                <value type="java.lang.Class">au.com.mail.domainobject.messagewrappers.MainQueueMessageWrapper
                </value>
            </constructor-arg>
        </bean>
    
        <bean id="processQueueItemProcessor"
            class="au.com.mail.batch.itemprocessors.CustomProcessQueueItemProcessor"
            scope="step" />
    
        <bean id="processQueueItemWriter"
            class="au.com.mail.batch.itemwriters.CustomQueueItemWriter"
            scope="step" />
    
        <bean id="defaultTaskExecutor"
            class="org.springframework.scheduling.quartz.SimpleThreadPoolTaskExecutor">
            <property name="threadCount" value="5" />
        </bean>
    
        <bean id="processQueueTaskExecutor"
            class="org.springframework.scheduling.quartz.SimpleThreadPoolTaskExecutor">
            <property name="threadCount" value="1" />
        </bean>
    
    
        <bean id="customStepExecutionListener" class="au.com.mail.batch.CustomStepExecutionListener"
            scope="step" />
    
        <bean id="jobLauncherTestUtils" class="org.springframework.batch.test.JobLauncherTestUtils">
            <property name="job" ref="fillQueue" />
            <property name="jobRepository" ref="jobRepository" />
            <property name="jobLauncher" ref="jobLauncher" />
        </bean>
    </beans>
    

    流程队列作业定义:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans:beans xmlns="http://www.springframework.org/schema/batch"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
            http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-2.2.xsd">
    
        <beans:bean id="repeatQueueOpenTasklet"
            class="au.com.mail.batch.CustomQueueRetryTaskletImpl" scope="step" />
    
        <job id="processQueue" job-repository="jobRepository">
            <step id="getQueue">
                <tasklet ref="repeatQueueOpenTasklet" task-executor="processQueueTaskExecutor">
                </tasklet>
                <end on="FAILED" exit-code="NOOP" />
                <next on="*" to="sendEmail" />
            </step>
            <step id="sendEmail">
                <tasklet task-executor="processQueueTaskExecutor">
                    <chunk reader="processQueueItemReader" processor="processQueueItemProcessor"
                        writer="processQueueItemWriter" commit-interval="5" />
                </tasklet>
                <listeners>
                    <listener ref="customStepExecutionListener"></listener>
                </listeners>
            </step>
        </job>
    </beans:beans>
    

    BIG BIG BIG UPDATE: 我删除了流程队列tasklet上的processQueueTaskExecutor,并从defaultTaskExecutor中删除了jobLauncher,并且亚马逊服务调用成功。现在我只需要知道为什么会这样。

2 个答案:

答案 0 :(得分:2)

从您的帖子中不清楚您的“单元”测试实际上是在尝试测试。可以安全地假设亚马逊的电子邮件服务经过了充分测试,无需在单元测试中进行实际测试。

相反,您可以为单元测试创​​建一个新的测试弹簧上下文,提供EmailService的模拟,然后在单元测试中验证实际上正在调用emailService.sendEmail(...)方法它与您期望的内容一样。这样,您的测试就不需要与任何实际的电子邮件服务进行交互。

答案 1 :(得分:1)

好的,我终于把它整理好了。这与我的单元/集成测试无关,而与多线程Spring Batch任务执行器有关。任务执行程序正在调用Amazon Email Service,而名为AwsSdkMetrics的类在主线程中锁定了一个名为useDefaultMetrics的同步方法。这意味着执行无法在任务执行程序内部继续执行,因此它等待主线程释放该同步方法。

所以我从ANT JVM中分离了我的jUnit JVM,一切都开始像魅力一样。