ユニットテスト
他のアプリケーションスタイルと同様に、バッチジョブの一部として記述されたコードを単体テストすることは非常に重要です。Spring のコアドキュメントには、Spring を使用した単体テストと統合テストの方法が詳細に記載されているため、ここでは繰り返しません。ただし、バッチジョブを「エンドツーエンド」でテストする方法について考えることが重要です。これについては、この章で説明します。spring-batch-test
プロジェクトには、このエンドツーエンドのテストアプローチを容易にするクラスが含まれています。
単体テストクラスの作成
単体テストでバッチジョブを実行するには、フレームワークがジョブの ApplicationContext
を読み込む必要があります。この動作をトリガーするために、2 つのアノテーションが使用されます。
@SpringJUnitConfig
は、クラスが Spring の JUnit 機能を使用する必要があることを示します@SpringBatchTest
は、テストコンテキストに Spring Batch テストユーティリティ (JobLauncherTestUtils
やJobRepositoryTestUtils
など) を挿入します。
テストコンテキストに単一の Job Bean 定義が含まれている場合、この Bean は JobLauncherTestUtils でオートワイヤーされます。それ以外の場合は、テスト中のジョブを手動で JobLauncherTestUtils に設定する必要があります。 |
Java
XML
次の Java の例は、使用中のアノテーションを示しています。
@SpringBatchTest
@SpringJUnitConfig(SkipSampleConfiguration.class)
public class SkipSampleFunctionalTests { ... }
次の XML の例は、使用中のアノテーションを示しています。
@SpringBatchTest
@SpringJUnitConfig(locations = { "/simple-job-launcher-context.xml",
"/jobs/skipSampleJob.xml" })
public class SkipSampleFunctionalTests { ... }
バッチジョブのエンドツーエンドテスト
「エンドツーエンド」テストは、バッチジョブの完全な実行を最初から最後までテストすることとして定義できます。これにより、テスト条件を設定し、ジョブを実行し、最終結果を検証するテストが可能になります。
データベースから読み取り、フラットファイルに書き込むバッチジョブの例を考えてみましょう。テストメソッドは、テストデータを使用してデータベースをセットアップすることから始まります。CUSTOMER
テーブルをクリアしてから、10 個の新しいレコードを挿入します。次に、テストは launchJob()
メソッドを使用して Job
を起動します。launchJob()
メソッドは、JobLauncherTestUtils
クラスによって提供されます。JobLauncherTestUtils
クラスは、テストで特定のパラメーターを指定できるようにする launchJob(JobParameters)
メソッドも提供します。launchJob()
メソッドは、Job
実行に関する特定の情報をアサートするのに役立つ JobExecution
オブジェクトを返します。次の場合、テストは Job
が COMPLETED
のステータスで終了したことを検証します。
Java
XML
次のリストは、Java 構成スタイルの JUnit 5 の例を示しています。
@SpringBatchTest
@SpringJUnitConfig(SkipSampleConfiguration.class)
public class SkipSampleFunctionalTests {
@Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
private JdbcTemplate jdbcTemplate;
@Autowired
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Test
public void testJob(@Autowired Job job) throws Exception {
this.jobLauncherTestUtils.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 = jobLauncherTestUtils.launchJob();
Assert.assertEquals("COMPLETED", jobExecution.getExitStatus().getExitCode());
}
}
次のリストは、XML 構成スタイルの JUnit 5 の例を示しています。
@SpringBatchTest
@SpringJUnitConfig(locations = { "/simple-job-launcher-context.xml",
"/jobs/skipSampleJob.xml" })
public class SkipSampleFunctionalTests {
@Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
private JdbcTemplate jdbcTemplate;
@Autowired
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Test
public void testJob(@Autowired Job job) throws Exception {
this.jobLauncherTestUtils.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 = jobLauncherTestUtils.launchJob();
Assert.assertEquals("COMPLETED", jobExecution.getExitStatus().getExitCode());
}
}
個々のステップのテスト
複雑なバッチジョブの場合、エンドツーエンドのテストアプローチのテストケースは管理不能になる可能性があります。これらの場合、個々のステップを単独でテストするためのテストケースを用意する方が便利です。JobLauncherTestUtils
クラスには、launchStep
と呼ばれるメソッドが含まれています。このメソッドは、ステップ名を取り、その特定の Step
のみを実行します。このアプローチにより、よりターゲットを絞ったテストが可能になり、テストはそのステップのみのデータを設定し、結果を直接検証できます。次の例は、launchStep
メソッドを使用して、名前で Step
をロードする方法を示しています。
JobExecution jobExecution = jobLauncherTestUtils.launchStep("loadFileStep");
ステップスコープコンポーネントのテスト
多くの場合、実行時にステップ用に構成されたコンポーネントは、ステップスコープと遅延バインディングを使用して、ステップまたはジョブの実行からコンテキストを注入します。コンテキストをステップ実行のように設定する方法がない限り、これらはスタンドアロンコンポーネントとしてテストするのが困難です。それが、Spring Batch の 2 つのコンポーネント StepScopeTestExecutionListener
と StepScopeTestUtils
のゴールです。
リスナーはクラスレベルで宣言され、そのロールは、次の例に示すように、各テストメソッドのステップ実行コンテキストを作成することです。
@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
が 2 つあります。1 つは通常の Spring Test フレームワークで、構成されたアプリケーションコンテキストからの依存性注入を処理してリーダーを注入します。もう 1 つは Spring Batch StepScopeTestExecutionListener
です。実行時に Step
で実行がアクティブであるかのように、それをテストメソッドのコンテキストとして使用して、StepExecution
のテストケースでファクトリメソッドを探すことによって機能します。ファクトリメソッドはその署名によって検出されます (それは StepExecution
を返す必要があります)。ファクトリメソッドが指定されていない場合は、デフォルトの StepExecution
が作成されます。
v4.1 から開始して、StepScopeTestExecutionListener
および JobScopeTestExecutionListener
は、テストクラスに @SpringBatchTest
アノテーションが付けられている場合、テスト実行リスナーとしてインポートされます。上記のテスト例は、次のように構成できます。
@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 は、出力ファイルの検証を容易にするために AssertFile
と呼ばれるクラスを提供します。assertFileEquals
と呼ばれるメソッドは、2 つの File
オブジェクト (または 2 つの Resource
オブジェクト) を取り、2 つのファイルが同じ内容を持っていることを 1 行ずつアサートします。次の例に示すように、期待される出力でファイルを作成し、それを実際の結果と比較することができます。
private static final String EXPECTED_FILE = "src/main/resources/data/input.txt";
private static final String OUTPUT_FILE = "target/test-outputs/output.txt";
AssertFile.assertFileEquals(new FileSystemResource(EXPECTED_FILE),
new FileSystemResource(OUTPUT_FILE));
ドメインオブジェクトのモック
Spring Batch コンポーネントの単体テストと統合テストを作成するときに発生するもう 1 つの一般的な課題は、ドメインオブジェクトをモックする方法です。次のコードスニペットが示すように、良い例は StepExecutionListener
です。
public class NoWorkFoundStepExecutionListener extends StepExecutionListenerSupport {
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
を作成するには、StepExecution
には JobInstance
と JobParameters
を必要とする JobExecution
が必要です。これは堅実なドメインモデルでは有効ですが、単体テスト用のスタブオブジェクトの作成が冗長になります。この課題に対処するために、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
を作成する前述のメソッドは、ファクトリ内で使用できる便利なメソッドの 1 つにすぎません。Javadoc で完全なメソッドのリストを見つけることができます。