Spring Batch 单元测试实战
本文基于之前的 SpringBatch 多写示例,展示如何编写针对性的单元测试。本示例不使用 @SpringBootTest
,从而避免启动完整服务上下文;同时,为了简化测试流程,我们对 Reader 进行了 Mock。
在本测试示例中,我们使用了以下 Spring 注解和配置:
- @EnableBatchProcessing:启用 Spring Batch 所需的核心组件。
- @TestConfiguration:定义测试专用的配置类。
- @ContextConfiguration:指定测试加载的配置类。
- @ExtendWith(SpringExtension.class):激活 Spring 测试支持,使得 Spring 上下文能够在测试中加载。
Job单元测试代码示例
下面代码示例,包含对 JobRepository、JobLauncher、StepBuilderFactory 及 JobBuilderFactory 的模拟配置,以及对 Reader 的 Mock 处理:
java
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {MultiWriteTestJobConfiguration.class, Job1Test.MockSpringBatchConfig.class})
@EnableBatchProcessing
public class Job1Test {
@Autowired
private JobLauncher jobLauncher;
@Autowired
private Job job1;
@Autowired
private Job job2;
@TestConfiguration
static class MockSpringBatchConfig {
@Bean
@Primary
public JobRepository jobRepository() {
try {
return new MapJobRepositoryFactoryBean().getObject();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Bean
public JobLauncher jobLauncher(JobRepository jobRepository) {
SimpleJobLauncher launcher = new SimpleJobLauncher();
launcher.setJobRepository(jobRepository);
return launcher;
}
@Bean
public StepBuilderFactory stepBuilderFactory(JobRepository jobRepository) {
return new StepBuilderFactory(jobRepository, new ResourcelessTransactionManager());
}
@Bean
public JobBuilderFactory jobBuilderFactory(JobRepository jobRepository) {
return new JobBuilderFactory(jobRepository);
}
}
// 使用 @MockBean 对 Reader 进行 Mock,避免依赖实际数据库数据
@MockBean(name = "myReader")
private JdbcPagingItemReader<OrderEntity> myReader;
@BeforeEach
public void setUp() throws Exception {
OrderEntity order1 = new OrderEntity();
order1.setId(1L);
order1.setAmount(new BigDecimal("1"));
order1.setChannelId("WEIXIN");
OrderEntity order2 = new OrderEntity();
order2.setId(2L);
order2.setAmount(new BigDecimal("2"));
order2.setChannelId("WEIXIN");
OrderEntity order3 = new OrderEntity();
order3.setId(3L);
order3.setAmount(new BigDecimal("3"));
order3.setChannelId("ZHIFUBAO");
List<OrderEntity> mockOrders = Arrays.asList(order1, order2, order3);
// 配置 Mock 行为:依次返回三个 OrderEntity 对象,随后返回 null 表示数据读取完毕
when(myReader.read()).thenReturn(mockOrders.get(0), mockOrders.get(1), mockOrders.get(2), null);
}
@Test
public void testJob() throws Exception {
JobExecution jobExecution = jobLauncher.run(job2, new JobParameters());
assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus());
}
}
Reader和Writer的单元测试
Reader 单元测试
java
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {TestReaderConfig.class})
public class ReaderTest {
@Autowired
private JdbcPagingItemReader<OrderEntity> myReader;
@Autowired
private DataSource dataSource;
@BeforeEach
public void setUpDatabase() throws Exception {
// 在内存数据库中创建表,并插入测试数据
try (Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement()) {
stmt.execute("CREATE TABLE order_table (" +
"id BIGINT, " +
"channel_id VARCHAR(50), " +
"order_no VARCHAR(50), " +
"amount DECIMAL(10,2)" +
")");
stmt.execute("INSERT INTO order_table VALUES (1, 'WEIXIN', 'order1', 1.00)");
stmt.execute("INSERT INTO order_table VALUES (2, 'ZHIFUBAO', 'order2', 2.00)");
stmt.execute("INSERT INTO order_table VALUES (3, 'WEIXIN', 'order3', 3.00)");
}
}
@Test
public void testReader() throws Exception {
ExecutionContext executionContext = new ExecutionContext();
myReader.open(executionContext);
List<OrderEntity> orders = new ArrayList<>();
OrderEntity order;
while ((order = myReader.read()) != null) {
orders.add(order);
}
myReader.close();
// 校验读取到的数据条数及部分数据内容
assertEquals(3, orders.size());
assertEquals("WEIXIN", orders.get(0).getChannelId());
assertEquals(new BigDecimal("2.00"), orders.get(1).getAmount());
}
}
@Configuration
@Import(MultiWriteTestJobConfiguration.class)
class TestReaderConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
}
Writer 单元测试
java
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {TestWriterConfig.class})
public class WriterTest {
@Autowired
private FlatFileItemWriter<OrderEntity> writer;
private Path tempFile;
@BeforeEach
public void setUp() throws Exception {
// 创建临时文件作为输出目标
tempFile = Files.createTempFile("orders_test", ".csv");
writer.setResource(new FileSystemResource(tempFile.toFile()));
}
@Test
public void testWriter() throws Exception {
// 准备测试数据
OrderEntity order = new OrderEntity();
order.setId(1L);
order.setChannelId("WEIXIN");
order.setAmount(new BigDecimal("1"));
List<OrderEntity> orders = Collections.singletonList(order);
// 打开 writer 并写入数据
ExecutionContext executionContext = new ExecutionContext();
writer.open(executionContext);
writer.write(orders);
writer.close();
// 读取临时文件内容并进行校验
List<String> lines = Files.readAllLines(tempFile);
assertFalse(lines.isEmpty());
String firstLine = lines.get(0);
assertTrue(firstLine.contains("1"));
assertTrue(firstLine.contains("WEIXIN"));
assertTrue(firstLine.contains("1"));
}
}
@Configuration
@Import(MultiWriteTestJobConfiguration.class)
class TestWriterConfig {
@Bean
@Primary
public FlatFileItemWriter<OrderEntity> testWriter() {
return new FlatFileItemWriterBuilder<OrderEntity>()
.name("testOrderItemWriter")
.resource(new FileSystemResource("target/test-output/orders_test.csv"))
.delimited()
.delimiter(",")
.names("id", "channelId", "amount")
.build();
}
}