好吧,我知道这从 Headers 听起来很简单,但它确实让我难以理解为什么会发生这种情况 .
所以,我'm using Spring Batch to generate emails to be sent using Amazon'的简单电子邮件服务 . 在我的 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封电子邮件到亚马逊电子邮件模拟器 .
我将在下面发布一个简略的类图,这样你就可以看到事情是如何结合在一起的 .
-
EmailService界面是 my own interface .
-
AmazonEmailService是 my own service . 此服务实际上使用AmazonSimpleEmailServiceClient执行电子邮件的发送,AmazonSimpleEmailServiceClient是Amazon提供给我的对象 .
-
CustomItemProcessor是我自己的对象,它实现了ItemProcessor接口 . 这是Spring Batch用于实际处理批处理作业中的项目的内容 . 电子邮件应该由此类生成和发送 .
-
AmazonEmailServiceTest只是一个单元测试,用于测试AmazonEmailService实际发送电子邮件的能力 .
在考虑为什么我遇到这个愚蠢的问题时你可能会想到的事情:
-
我已正确配置单元/集成测试以在Spring应用程序上下文中运行 .
-
AmazonEmailServiceTest
自行成功运行 . -
如果我恢复使用简单的JavaMail电子邮件发件人,则Spring Batch集成测试会成功运行 .
-
我的Spring Batch作业,应用程序上下文和bean都是使用XML配置为我的所有其他类正确配置和定义的 .
-
我的亚马逊凭证有效且工作正常 .
-
类正确自动装配 .
-
在尝试单独或使用
AmazonEmailServiceTest
运行Spring Batch测试的任何阶段都不会引发异常 -
当一起运行测试时,
AmazonEmailService
的同一个实例被自动连接到两个测试中(即两个测试的相同内存地址等) -
我无法检查实际单元测试中使用的凭据 .
-
我在XML配置中正确配置了PropertyPlaceholderConfigurer .
-
对
amazonSimpleEmailServiceClient.send(emailRequest)
的调用将挂起,直到我手动停止单元测试 . -
相关的bean在自动装配的应用程序上下文中是可发现的 .
如果您需要更多信息,如课程或配置,请不要犹豫 . 我真的坐在电脑前等待回复 .
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
,并从 jobLauncher
中删除了 defaultTaskExecutor
,并且亚马逊服务调用成功 . 现在我只需要知道为什么会这样 .
2 回答
从您的帖子中不清楚您的“单元”测试实际上是在尝试测试 . 可以安全地假设亚马逊的电子邮件服务经过了充分测试,并且无需在您的单元测试中进行实际测试 .
相反,您可以为单元测试创建一个新的测试 spring 上下文,提供模拟
EmailService
,然后在您的单元测试中验证emailService.sendEmail(...)
方法实际上是在您预期的情况下被调用的,具有您期望的内容 . 这样您的测试就不需要与任何实际的电子邮件服务进行交互好的,我终于把它全部排除了 . 这与我的单元/集成测试无关,而与多线程Spring Batch任务 Actuator 有关 . 任务执行程序正在调用Amazon Email Service,而名为AwsSdkMetrics的类在主线程中锁定了一个名为useDefaultMetrics的同步方法 . 这意味着执行无法在任务执行程序内部继续执行,因此它等待主线程释放该同步方法 .
所以我从ANT JVM中分离了我的jUnit JVM,一切都像魅力一样开始工作 .