2014-04-15 2 views
2

좋아, 이건 제목에서 간단하게 들리 겠지만 실제로 이런 일이 벌어지는 이유에 관해서는 저를 잘 모릅니다.스프링 배치 작업에 대한 통합 테스트에 실패했습니다.

그래서 스프링 일괄 처리를 사용하여 Amazon의 Simple Email Service를 사용하여 이메일을 전송합니다. 내 CustomItemProcessor 안에 @Autowired을 사용하여 정상적으로 내 AmazonEmailService 서비스에 연결합니다. AmazonEmailService 클래스는 내 EmailSender 인터페이스를 구현합니다.

AmazonEmailService은 실제로 Amazon Simple Email Service를 호출하여 작업을 수행하는 데 사용되는 @Autowired입니다.

AmazonSimpleEmailServiceClient 빈은 내 루트-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> 

이 모든 것이 잘 작동합니다.

내 문제는 스프링 배치 작업 통합 테스트를 실행할 때입니다. 전자 메일을 보낼 때 응답하지 않습니다. 나의 로깅은 amazonSimpleEmailServiceClient.send(emailRequest)에 대한 호출에서 실행이 멈추고 계속 진행되지 않음을 보여줍니다.

저를 절대적으로 모욕 한 점은 스프링 배치 작업을위한 통합 테스트를 실행하기 전에 AmazonEmailService 유닛 테스트를 실행하면 모든 것이 성공적으로 완료된다는 것입니다. 왜 이것이 사실인지 알고 싶습니다. 실제로 전자 메일을 전송하는 배치 작업에는 단일 스레드에서 실행되는 TaskExecutor이 있다는 것을 언급 할만한 가치가 있습니다.

스프링 배치 작업의 통합 테스트는 작업이 수신하는 모든 입력에 대해 이메일을 성공적으로 생성하고 Amazon SES를 사용하여 해당 이메일을 보낼 수 있는지 확인합니다. 또한 올바르게 설정된 큐에 개체를 읽고 쓰는 지 테스트하지만 내 문제와 관련이 없습니다. AmazonEmailService의 단위 테스트는 아마존 전자 메일 시뮬레이터에 3 개의 전자 메일을 보냅니다.

아래에 요약 된 클래스 다이어그램을 게시하여 어떻게 서로 달라 붙는 지 확인할 수 있습니다.

Class Diagram

  • EmailService 인터페이스

    내 자신의 인터페이스입니다.
  • AmazonEmailService는 내 서비스입니다. 이 서비스는 실제로 Amazon에서받은 객체 인 AmazonSimpleEmailServiceClient를 사용하여 전자 메일을 전송합니다.
  • CustomItemProcessor는 ItemProcessor 인터페이스를 구현하는 내 자신의 개체입니다. Spring 배치가 일괄 작업의 항목을 실제로 처리하기 위해 사용하는 것입니다. 전자 메일은이 클래스에서 생성되어 전송되어야합니다.
  • AmazonEmailServiceTest는 아마 AmazonEmailService의 능력을 테스트하여 전자 메일을 실제로 보내는 단위 테스트 일뿐입니다. 나는이 바보 같은 문제가 발생하고 이유를 고려할 때 가정 할 수

것들 :

  1. 나는 제대로 Spring 애플리케이션 컨텍스트에서 실행하는 내 단위/통합 테스트를 구성했습니다.
  2. AmazonEmailServiceTest이 성공적으로 실행됩니다.
  3. 간단한 JavaMail 이메일 발신자를 사용하면 스프링 배치 통합 테스트가 성공적으로 실행됩니다.
  4. My Spring Batch 작업, 응용 프로그램 컨텍스트 및 빈은 모든 다른 클래스에 대해 XML 구성을 사용하여 올바르게 구성되고 정의됩니다.
  5. 내 Amazon 자격 증명이 유효하고 올바르게 작동합니다.
  6. 클래스는 자동으로 올바르게 입력됩니다.
  7. 예외가 함께 테스트를 실행하면, AmazonEmailService의 동일한 인스턴스가 두 테스트에 autowire가되어 분리 또는 AmazonEmailServiceTest
  8. 으로 어느 스프링 배치 테스트를 실행하려고의 모든 단계에서 발생되지 않고 (즉, 동일한 메모리 주소 두 테스트 모두)
  9. 실제 단위 테스트에 사용 된 자격 증명을 확인할 방법이 없습니다.
  10. XML 구성 내에 PropertyPlaceholderConfigurer를 올바르게 구성했습니다.
  11. 단위 테스트를 수동으로 중지 할 때까지 amazonSimpleEmailServiceClient.send(emailRequest)에 대한 호출이 중단됩니다.
  12. 관련 빈은 자동 와이어 링을위한 응용 컨텍스트에서 검색 할 수 있습니다.

클래스 나 설정과 같은 정보가 더 필요하면 언제든지 물어보십시오. 나는 문자 그대로 응답을 기다리는 내 컴퓨터 앞에 앉아있다.

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

나는 Spring 애플리케이션 컨텍스트에서 실행되도록 내 단위 테스트를 구성하는 곳 SpringTest 클래스입니다. My MockObjectFactory는 이름에서 알 수 있듯이 테스트 객체를 생성하는 정적 메서드가 포함 된 클래스입니다.

배치 서블릿 :

<?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 : 나는 jobLauncher에서 defaultTaskExecutor를 제거 내 processQueueTaskExecutor 프로세스 큐 태스크 릿에 제거 Amazon 서비스 호출이 성공합니다. 이제는 왜 이것이 사실인지 알 필요가 있습니다.

+1

AWS 서비스는 AmazonSimpleEmailServiceClient의 후속 사용이 인증을 건너 뛰고 더 빨리 작동 할 수있게 해주는 "remember-me"인증 메커니즘과 유사한 방식 일 수 있습니다. 두 번째 생각은 AWS 서비스가 블로킹이고 JUnit이 실수가 아닌 경우 별도의 스레드에서 각 테스트를 실행한다는 것입니다. 나는 둘 사이의 연결을 보지 못했지만, 그것은 단지 생각이다. –

+0

@AndreiStefan - 내가 생각했던 것과 비슷하지만 15 분 동안 Batch Jobs 테스트를 실행하고 아무데도 나가지 못했습니다. 또한 제 2 일괄 작업 중 단 하나만 실제로 Amazon 이메일 서비스를 공격합니다. AWS는 서비스 호출이 완료 될 때까지 차단하지만, 서비스가 시작되었거나 오류 메시지가 다시 나타나는지 확인하는 방법을 모릅니다. 나는 나보다 다른 누군가가 이것이 정말로 이상한 오류라고 생각하기 때문에 기쁘다. – JamesENL

+2

여러 구성 요소가 어떻게 협력하는지 테스트 중이므로 * 통합 * 테스트가 아닌 * 단위 테스트가 유용 할 수 있습니다. – kryger

답변

1

좋아, 마침내 그것을 전부 다 정리했습니다. 그것은 단위/통합 테스트 및 다중 스레드 스프링 배치 태스크 실행자와 관련된 모든 것과 관련이 없습니다. AwsSdkMetrics라는 클래스가 주 스레드에서 useDefaultMetrics라는 동기화 된 메소드에 대한 잠금을 가지고있는 동안 태스크 실행자는 Amazon 이메일 서비스를 호출했습니다. 이는 실행이 태스크 실행기 내에서 진행될 수 없으므로 메인 스레드가 동기화 된 메소드를 릴리스 할 때까지 기다리는 것을 중단 시켰음을 의미합니다.

그래서 ANT JVM에서 내 jUnit JVM을 fork하고 모든 것이 매력처럼 작동하기 시작했습니다.

2

게시물에서 "단위 테스트"가 실제로 테스트하려고하는 것이 분명하지 않습니다. 아마존의 이메일 서비스가 잘 테스트되고 단원 테스트에서 실제로 테스트 될 필요가 없다고 가정하는 것이 안전합니다.

EmailService 모의를 제공하는 단위 테스트에 대한 새 테스트 봄 컨텍스트를 만든 다음 내용에 따라 emailService.sendEmail(...) 메서드가 실제로 호출되고 있는지를 단위 테스트에서 확인할 수 있습니다 너는 기대한다.이렇게하면 실제 전자 메일 서비스와 상호 작용할 필요가 없습니다.

+0

죄송합니다. 내 단위 테스트의 핵심은 Spring Batch 작업이 실제로 전자 메일로받은 모든 항목을 처리하는지 확인하고 Amazon SES를 사용하여 해당 전자 메일이 실제로 보내지는지 확인하는 것입니다. 이 테스트가 실패한다는 사실은 심각한 문제입니다. SES를 사용하여 이러한 이메일을 보낼 수 있어야하기 때문입니다. – JamesENL

+0

일괄 처리 단위 테스트가 – JamesENL

+0

이고, EmailService 인터페이스가 내 인터페이스 인 것에 대한 설명을 포함하도록 내 질문을 업데이트했습니다. 아마존은 저에게주지 않았습니다. – JamesENL

관련 문제