单元测试

与其他应用风格一样,对任何编写的代码进行单元测试极为重要 作为批量作业的一部分。Spring核心文档涵盖了如何进行单位化和集成 用Spring做了非常详细的测试,所以这里不会重复。然而,这很重要, 思考如何“端到端”测试批次作业,这正是本章所涵盖的内容。 这Spring Batch测试项目包含促进这一端到端测试的课程 方法。spring-doc.cadn.net.cn

创建单元测试类

为了让单元测试运行批处理作业,框架必须加载该作业的应用上下文.触发该行为使用了两种注释:spring-doc.cadn.net.cn

  • @SpringJUnitConfig表示该班应该使用 Spring 的 JUnit 设施spring-doc.cadn.net.cn

  • @SpringBatchTest注入 Spring Batch 测试工具(例如JobOperatorTestUtilsJobRepositoryTestUtils)在测试语境中spring-doc.cadn.net.cn

如果测试上下文包含单个工作豆子的定义,就是这个 豆子会被自动接线JobOperatorTestUtils.否则,工作就好 被测时应手动设置JobOperatorTestUtils.
自 Spring Batch 6.0 起,JUnit 4 已不再支持。建议迁移到朱尼特木星。

以下 Java 示例展示了所使用的注释:spring-doc.cadn.net.cn

使用 Java 配置
@SpringBatchTest
@SpringJUnitConfig(SkipSampleConfiguration.class)
public class SkipSampleFunctionalTests { ... }

以下XML示例展示了所使用的注释:spring-doc.cadn.net.cn

使用 XML 配置
@SpringBatchTest
@SpringJUnitConfig(locations = { "/skip-sample-configuration.xml" })
public class SkipSampleFunctionalTests { ... }

批处理作业的端到端测试

“端到端”测试可以定义为测试一个批处理作业的整个运行过程,从 从头到尾。这允许测试设置测试条件,执行作业, 并验证最终结果。spring-doc.cadn.net.cn

举一个批处理作业的例子,它从数据库读取并写入一个平面文件。 测试方法首先是用测试数据建立数据库。它清除了客户端然后插入10条新记录。测试随后发射工作通过使用srartJob()方法。 这srartJob()方法由JobOperatorTestUtils类。 这JobOperatorTestUtils类还提供startJob(JobParameters)该方法允许检验给出特定参数。 这srartJob()方法 返回作业执行对象,用于断言特定信息关于工作跑。 在以下情况下,检验验证了工作结尾为状态为完成.spring-doc.cadn.net.cn

以下列表展示了 Java 配置风格下的 JUnit 5 示例:spring-doc.cadn.net.cn

基于 Java 的配置
@SpringBatchTest
@SpringJUnitConfig(SkipSampleConfiguration.class)
public class SkipSampleFunctionalTests {

    @Autowired
    private JobOperatorTestUtils jobOperatorTestUtils;

    private JdbcTemplate jdbcTemplate;

    @Autowired
    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Test
    public void testJob(@Autowired Job job) throws Exception {
        this.jobOperatorTestUtils.setJob(job);
        this.jdbcTemplate.update("delete from CUSTOMER");
        for (int i = 1; i <= 10; i++) {
            this.jdbcTemplate.update("insert into CUSTOMER values (?, 0, ?, 100000)",
                                      i, "customer" + i);
        }

        JobExecution jobExecution = jobOperatorTestUtils.startJob();


        Assert.assertEquals("COMPLETED", jobExecution.getExitStatus().getExitCode());
    }
}

以下列表展示了 JUnit 5 的 XML 配置样式示例:spring-doc.cadn.net.cn

基于XML的配置
@SpringBatchTest
@SpringJUnitConfig(locations = { "/skip-sample-configuration.xml" })
public class SkipSampleFunctionalTests {

    @Autowired
    private JobOperatorTestUtils jobOperatorTestUtils;

    private JdbcTemplate jdbcTemplate;

    @Autowired
    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Test
    public void testJob(@Autowired Job job) throws Exception {
        this.jobOperatorTestUtils.setJob(job);
        this.jdbcTemplate.update("delete from CUSTOMER");
        for (int i = 1; i <= 10; i++) {
            this.jdbcTemplate.update("insert into CUSTOMER values (?, 0, ?, 100000)",
                                      i, "customer" + i);
        }

        JobExecution jobExecution = jobOperatorTestUtils.startJob();


        Assert.assertEquals("COMPLETED", jobExecution.getExitStatus().getExitCode());
    }
}

测试单个步骤

对于复杂的批处理作业,端到端测试方法中的测试用例可能会变成 不可收拾。 在这些情况下,使用测试用例单独测试步骤可能更有用。 这JobOperatorTestUtils类包含一个称为launchStep, 该步长取一个步名,并运行该步. 这种方法允许更针对性的测试,使测试仅为该步骤设置数据,并直接验证其结果。以下示例展示了如何使用startStep启动方法姓名:spring-doc.cadn.net.cn

JobExecution jobExecution = jobOperatorTestUtils.startStep("loadFileStep");

测试步骤范围组件

通常,为你的步骤配置的组件会使用步骤作用域和延迟绑定来从步骤或作业执行中注入上下文。这些很难测试为独立组件,除非你有办法把上下文设置成它们在某个步骤里 执行。 这是Spring Batch两个组成部分的目标:StepScopeTestExecutionListenerStepScopeTestUtils.spring-doc.cadn.net.cn

监听器在类级声明,其任务是为每个测试方法创建上下文,如下示例所示:spring-doc.cadn.net.cn

@SpringJUnitConfig
@TestExecutionListeners( { DependencyInjectionTestExecutionListener.class,
    StepScopeTestExecutionListener.class })
public class StepScopeTestExecutionListenerIntegrationTests {

    // This component is defined step-scoped, so it cannot be injected unless
    // a step is active...
    @Autowired
    private ItemReader<String> reader;

    public StepExecution getStepExecution() {
        StepExecution execution = MetaDataInstanceFactory.createStepExecution();
        execution.getExecutionContext().putString("input.data", "foo,bar,spam");
        return execution;
    }

    @Test
    public void testReader() {
        // The reader is initialized and bound to the input data
        assertNotNull(reader.read());
    }

}

有两个测试执行监听器. 一种是常规的Spring测试框架,它处理从配置的应用上下文中注入依赖以注入读卡器。另一种是Spring批处理StepScopeTestExecutionListener. 它的工作原理是寻找在测试用例中的工厂方法步执行,并以此为上下文测试方法,仿佛该执行在运行时。工厂方法通过其签名检测(必须返回步执行). 如果工厂方法是未提供,则默认步执行被创造出来。spring-doc.cadn.net.cn

从 v4.1 开始,StepScopeTestExecutionListenerJobScopeTestExecutionListener作为测试执行监听器被导入如果测试类被注释为@SpringBatchTest. 前述测试示例可配置如下:spring-doc.cadn.net.cn

@SpringBatchTest
@SpringJUnitConfig
public class StepScopeTestExecutionListenerIntegrationTests {

    // This component is defined step-scoped, so it cannot be injected unless
    // a step is active...
    @Autowired
    private ItemReader<String> reader;

    public StepExecution getStepExecution() {
        StepExecution execution = MetaDataInstanceFactory.createStepExecution();
        execution.getExecutionContext().putString("input.data", "foo,bar,spam");
        return execution;
    }

    @Test
    public void testReader() {
        // The reader is initialized and bound to the input data
        assertNotNull(reader.read());
    }

}

如果你希望步长范围的持续时间是测试方法的执行,监听器方法很方便。测试方法的执行。如果想要更灵活但更具侵入性的方法,你可以使用 这StepScopeTestUtils. 以下示例统计了 中可用的项目数量前例所示的阅读器:spring-doc.cadn.net.cn

int count = StepScopeTestUtils.doInStepScope(stepExecution,
    new Callable<Integer>() {
      public Integer call() throws Exception {

        int count = 0;

        while (reader.read() != null) {
           count++;
        }
        return count;
    }
});

模拟域对象

在为 Spring Batch 编写单元测试和集成测试时,另一个常见问题是组件是如何模拟域对象。一个很好的例子是StepExecutionListener如 以下代码片段显示:spring-doc.cadn.net.cn

public class NoWorkFoundStepExecutionListener implements StepExecutionListener {

    public ExitStatus afterStep(StepExecution stepExecution) {
        if (stepExecution.getReadCount() == 0) {
            return ExitStatus.FAILED;
        }
        return null;
    }
}

该框架提供了前面的监听者示例,并检查步执行对于空的读取计数,因此表示没有完成任何工作。虽然这个例子相当简单,但它用来说明当你尝试实现需要 Spring Batch 域接口的单元测试类时,可能会遇到的问题类型 对象。 考虑前述示例中听者的单元测试:spring-doc.cadn.net.cn

private NoWorkFoundStepExecutionListener tested = new NoWorkFoundStepExecutionListener();

@Test
public void noWork() {
    StepExecution stepExecution = new StepExecution("NoProcessingStep",
                new JobExecution(new JobInstance(1L, new JobParameters(),
                                 "NoProcessingJob")));

    stepExecution.setExitStatus(ExitStatus.COMPLETED);
    stepExecution.setReadCount(0);

    ExitStatus exitStatus = tested.afterStep(stepExecution);
    assertEquals(ExitStatus.FAILED.getExitCode(), exitStatus.getExitCode());
}

由于Spring Batch域模型遵循了良好的面向对象原则,步执行需要一个作业执行,这需要一个JobInstance作业参数,以创建有效的步执行. 虽然这在稳固的领域模型中是好的,但它确实使创建单元测试的存根对象变得冗长冗长。为了解决这个问题,Spring Batch 测试模块包含了一个用于创建域对象的工厂:MetaDataInstanceFactory. 基于该工厂,单元测试可以更新为更简洁,如下示例所示:spring-doc.cadn.net.cn

private NoWorkFoundStepExecutionListener tested = new NoWorkFoundStepExecutionListener();

@Test
public void testAfterStep() {
    StepExecution stepExecution = MetaDataInstanceFactory.createStepExecution();

    stepExecution.setExitStatus(ExitStatus.COMPLETED);
    stepExecution.setReadCount(0);

    ExitStatus exitStatus = tested.afterStep(stepExecution);
    assertEquals(ExitStatus.FAILED.getExitCode(), exitStatus.getExitCode());
}

前述创建简单方法步执行仅有一种便利方法,在工厂内可用。你可以在其Java文档中找到完整的方法列表。spring-doc.cadn.net.cn