單元測試

與其他應用程式樣式一樣,對作為批處理作業一部分編寫的任何程式碼進行單元測試都極其重要。Spring 核心文件詳細介紹瞭如何使用 Spring 進行單元測試和整合測試,因此在此不再贅述。然而,重要的是要考慮如何“端到端”測試批處理作業,這正是本章所涵蓋的內容。spring-batch-test 專案包含方便這種端到端測試方法的類。

建立單元測試類

為了使單元測試執行批處理作業,框架必須載入作業的 ApplicationContext。使用兩個註解來觸發此行為

  • @SpringJUnitConfig 指示類應使用 Spring 的 JUnit 功能

  • @SpringBatchTest 在測試上下文中注入 Spring Batch 測試實用程式(例如 JobOperatorTestUtilsJobRepositoryTestUtils

如果測試上下文包含單個 Job bean 定義,此 bean 將自動裝配到 JobOperatorTestUtils 中。否則,被測試的作業應手動設定到 JobOperatorTestUtils 中。
自 Spring Batch 6.0 起,不再支援 JUnit 4。建議遷移到 JUnit Jupiter。
  • Java

  • XML

以下 Java 示例顯示了正在使用的註解

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

以下 XML 示例顯示了正在使用的註解

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

批處理作業的端到端測試

“端到端”測試可以定義為從頭到尾測試批處理作業的完整執行。這允許設定測試條件、執行作業並驗證最終結果的測試。

考慮一個從資料庫讀取並寫入平面檔案的批處理作業示例。測試方法首先使用測試資料設定資料庫。它清除 CUSTOMER 表,然後插入 10 條新記錄。然後,測試使用 startJob() 方法啟動 JobstartJob() 方法由 JobOperatorTestUtils 類提供。JobOperatorTestUtils 類還提供了 startJob(JobParameters) 方法,該方法允許測試提供特定引數。startJob() 方法返回 JobExecution 物件,這對於斷言有關 Job 執行的特定資訊很有用。在以下情況下,測試驗證 JobCOMPLETED 狀態結束。

  • Java

  • XML

以下清單顯示了採用 Java 配置樣式的 JUnit 5 示例

基於 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());
    }
}

以下清單顯示了採用 XML 配置樣式的 JUnit 5 示例

基於 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 的方法,該方法接受一個步驟名稱並僅執行該特定 Step。這種方法允許進行更有針對性的測試,讓測試僅為該步驟設定資料並直接驗證其結果。以下示例展示瞭如何使用 startStep 方法按名稱啟動 Step

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

測試步驟範圍元件

通常,在執行時為您的步驟配置的元件使用步驟範圍和後期繫結來從步驟或作業執行中注入上下文。除非您有一種方法可以在它們處於步驟執行中時設定上下文,否則這些元件很難作為獨立元件進行測試。這是 Spring Batch 中兩個元件的目標:StepScopeTestExecutionListenerStepScopeTestUtils

監聽器在類級別宣告,其作用是為每個測試方法建立步驟執行上下文,如下例所示

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

}

有兩個 TestExecutionListeners。一個是常規的 Spring 測試框架,它處理從配置的應用程式上下文進行依賴注入以注入讀取器。另一個是 Spring Batch StepScopeTestExecutionListener。它的工作原理是在測試用例中查詢 StepExecution 的工廠方法,將其用作測試方法的上下文,就好像該執行在執行時在 Step 中處於活動狀態一樣。工廠方法透過其簽名檢測到(它必須返回 StepExecution)。如果沒有提供工廠方法,則會建立一個預設的 StepExecution

從 v4.1 開始,如果測試類使用 @SpringBatchTest 註解,則 StepScopeTestExecutionListenerJobScopeTestExecutionListener 將作為測試執行監聽器匯入。前面的測試示例可以按如下方式配置

@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。以下示例計算前面示例中顯示的讀取器中可用項的數量

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,如下程式碼片段所示

public class NoWorkFoundStepExecutionListener implements StepExecutionListener {

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

該框架提供了上述監聽器示例,並檢查 StepExecution 的空讀取計數,從而表示沒有完成任何工作。雖然這個示例相當簡單,但它有助於說明在嘗試單元測試實現需要 Spring Batch 領域物件的介面的類時可能遇到的問題。考慮以下針對前面示例中監聽器的單元測試

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 領域模型遵循良好的面向物件原則,StepExecution 需要 JobExecution,而 JobExecution 又需要 JobInstanceJobParameters 才能建立有效的 StepExecution。雖然這在堅實的領域模型中是好的,但它確實使得為單元測試建立存根物件變得冗長。為了解決這個問題,Spring Batch 測試模組包含一個用於建立領域物件的工廠:MetaDataInstanceFactory。有了這個工廠,單元測試可以更新得更簡潔,如下例所示

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

上述建立簡單 StepExecution 的方法只是工廠中可用的眾多便利方法之一。您可以在其 Javadoc 中找到完整的方法列表。

© . This site is unofficial and not affiliated with VMware.