※当サイトの記事には、広告・プロモーションが含まれます。

Spring Batchの単体テストを実施してみる

japan.zdnet.com

⇧ amazing...

Spring Batchの単体テストに必要な依存関係

公式のドキュメントでは見つけられなかったのですが、

www.baeldung.com

We included the spring-boot-starter-test and spring-batch-test which bring in some necessary helper methods, listeners and runners for testing Spring Batch applications.

https://www.baeldung.com/spring-batch-testing-job

⇧ 上記サイト様によりますと、

  • spring-boot-starter-test
  • spring-batch-test

の2つの依存関係があれば良いらしい。

JUnitとかかは、spring-boot-starter-testの依存関係に含まれてるらしい。

Spring Batchの単体テストを実施してみる

というわけで、

spring.pleiades.io

hiro-engineer-blog.com

⇧ 上記サイト様の情報を参考に、Spring Batchの単体テストを実施してみる。

利用するプロジェクトは、

ts0818.hatenablog.com

⇧ 上記の記事のものになります。

まずは、依存関係を追加で。spring-boot-starter-testはデフォルトで追加されてたので、spring-batch-testを追加で。

■/mybatis-example/build.gradle

plugins {
	id 'org.springframework.boot' version '2.7.5'
	id 'io.spring.dependency-management' version '1.0.15.RELEASE'
	id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	// https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter
    implementation group: 'org.mybatis.spring.boot', name: 'mybatis-spring-boot-starter', version: '2.2.2'
    // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-batch
    implementation group: 'org.springframework.boot', name: 'spring-boot-starter-batch', version: '2.7.5'
	implementation 'com.opencsv:opencsv:5.7.1'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	runtimeOnly 'org.postgresql:postgresql'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	implementation 'org.springframework.batch:spring-batch-test:4.3.7'
}

tasks.named('test') {
	useJUnitPlatform()
}

で、テストクラスを作成して実行するもエラー。

qiita.com

⇧ 上記サイト様によりますと、テストに合わせて本番のソースコードを修正する必要があるという本末転倒ぶりがSpring Frameworkということらしい、残念過ぎる...

テストに合わせて本番で正常に動いているコードを直すのは御法度だと思うのですが、残念ながらSpring Frameworkは、テストに合わせろってことらしいので合わせますか。

今回、変更、追加してるファイルは以下。

変更、追加の在ったファイルの内容を記載。

■/mybatis-example/src/main/java/com/example/demo/batch/user/UserBatchConfig.java

package com.example.demo.batch.user;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.job.flow.FlowExecutionStatus;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.FileSystemResource;

import com.example.demo.dto.CsvUser;
import com.example.demo.entity.ShikokuOhenroUser;

@Configuration
@EnableBatchProcessing
@ComponentScan("com.example.demo") 
public class UserBatchConfig {

	@Autowired
	private JobBuilderFactory jobBuilderFactory;
	
	@Autowired
	private StepBuilderFactory stepBuilderFactory;
	
//	@Bean
//	@StepScope
//	public Resource userCsvResource(@Value("#{jobParameters['filePath']}") String filePath) {
//		return new FileSystemResource(filePath);
//	}
	
    @Bean
    @StepScope
    public UserReader<CsvUser> csvUserReader(CsvUserMapper csvUserMapper, @Value("#{jobParameters['filePath']}") String filePath) throws IOException {
        UserReader<CsvUser> reader = new UserReader<>();
        reader.setCharset(StandardCharsets.UTF_8);
        reader.setStrict(true);
        //reader.setResource(new ClassPathResource(filePath));
        reader.setResource(new FileSystemResource(filePath));
        reader.setLinesToSkip(1);
        reader.setHeaders(new String[] {"last_name", "first_name", "rome_last_name", "rome_first_name", "ohenro_ids"});
        reader.setFieldSetMapper(csvUserMapper);
        reader.setDelimiter(',');
        reader.setQuotedChar('"');
        reader.setName("csvReader");
        return reader;
    }
    
    @Bean
    public Step step1(UserReader<CsvUser> userReader, UserWriter userWriter, UserProcessor userProcessor) {
        return stepBuilderFactory.get("csvItemReaderStep")
                .<CsvUser, ShikokuOhenroUser> chunk(10)
                .reader(userReader)
                .processor(userProcessor)
                .writer(userWriter)
                .build();
    }
    
//    @Bean
//    public Job importUserJob(Step step1) {
//    	return jobBuilderFactory.get("importUserJob")
//    			.incrementer(new RunIdIncrementer())
//    			.start(step1)
//    			.build();
//    }
    
    @Bean
    public Job userJob(Step step1) {
    	return jobBuilderFactory.get("userJob")
    			.incrementer(new RunIdIncrementer())
    			.start(step1)
    			  .next(decider())
    			  .on("Success")
    			  .end()
    			  .build()
    			.build();
    }

    @Bean
    public JobExecutionDecider decider() {
        System.out.println("Made it to the decider");
        return (jobExecution, stepExecution) -> new FlowExecutionStatus("Success"); 
    }

}    

■/mybatis-example/src/test/java/com/example/demo/batch/user/UserBatchTest.java

package com.example.demo.batch.user;

import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.*;

import java.nio.file.Paths;

import org.junit.After;
import org.junit.Before;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.batch.test.JobRepositoryTestUtils;
import org.springframework.batch.test.context.SpringBatchTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBatchTest
@SpringBootTest(classes= {UserBatchConfig.class})
//@EnableAutoConfiguration
//@ContextConfiguration(classes = { UserBatchConfig.class })
//@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class })
//@DirtiesContext(classMode = ClassMode.AFTER_CLASS)
public class UserBatchTest {
	
	private static final String TEST_INPUT = Paths.get("src/test/resources/test/test-input.csv").toAbsolutePath().toString();
    @Autowired
    private Job userJob;
	
    @Autowired
    private JobLauncherTestUtils jobLauncherTestUtils;
  
    @Autowired
    private JobRepositoryTestUtils jobRepositoryTestUtils;
    
    @Before
    public void before() {
        jobLauncherTestUtils.setJob(userJob);
    }
 
    @After
    public void cleanUp() {
        jobRepositoryTestUtils.removeJobExecutions();
    }
    
    private JobParameters defaultJobParameters() {
        JobParametersBuilder paramsBuilder = new JobParametersBuilder();
        paramsBuilder.addLong("time", System.currentTimeMillis());
        paramsBuilder.addString("filePath", TEST_INPUT);
         return paramsBuilder.toJobParameters();
    }
    
    @Test
    public void testJob() throws Exception {
 
        JobExecution jobExecution = jobLauncherTestUtils.launchJob(defaultJobParameters());
        //JobInstance actualJobInstance = jobExecution.getJobInstance();
        ExitStatus actualJobExitStatus = jobExecution.getExitStatus();   
        //assertThat(actualJobInstance.getJobName(), is("transformBooksRecords"));
        assertThat(actualJobExitStatus.getExitCode(), is("COMPLETED"));
    }
}

■/mybatis-example/src/test/resources/test/test-input.csv

last_name,first_name,rome_last_name,rome_first_name,ohenro_ids
"鈴木","二郎","suzuki","jiro","1,2,3,4,5,6"
"鈴木","三郎","suzuki","saburo","24,25,26,27,28"
"鈴木","四郎","suzuki","shiro","40,41,42,43,44,45,46"
"鈴木","五郎","suzuki","goro","81,82,83,84,85,86"
"鈴木","六郎","suzuki","rokuro","1,2,3,4,5,6"
"鈴木","七郎","suzuki","shichiro","24,25,26,27,28,29"
"鈴木","八郎","suzuki","hachiro","40,41,42,43,44,45,46,47"
"鈴木","九郎","suzuki","kyuro","81,82,83,84,85,86"
"鈴木","十郎","suzuki","jyuro","1,2,3,4,5,6"    

で、「パッケージ・エクスプローラー」でテストクラスを選択した状態で右クリックし、「実行(R)」>「JUnit テスト」を選択。

■テスト実行前

■テスト実行後

テストデータのCSVファイルのデータがINSERTされました。

何とか、バッチ処理単体テストが実施できたようです。

それにしても、公式のドキュメントの通り実施しても動かないとか、最早ドキュメントの意味無いですね...

毎度モヤモヤ感が半端ない...

今回はこのへんで。