https://docs.spring.io/spring-batch/4.1.x/reference/html/testing.html#testing
https://qiita.com/kagamihoge/items/12fbbc2eac5b8a5ac1e0 俺の訳一覧リスト
1. Unit Testing
バッチ以外のアプリケーション同様に、バッチジョブの一部として書いたコードのユニットテストは極めて重要です。SpringのコアドキュメントにSpringでのユニットおよびインテグレーションテストの方法が良くまとめられているため、ここではそれについては触れません。ここでは、バッチジョブの'end to end'なテスト方法について解説します。spring-batch-testにはend-to-endのテスト手法を行うためのクラスがあります。
1.1. Creating a Unit Test Class
バッチジョブをユニットテストとして動作させるには、フレームワークでジョブのApplicationContext
をロードする必要があります。これを行うための2つのアノテーションがあります。
@RunWith(SpringRunner.class)
: SpringのJUnit機能を使用するためのアノテーション。@ContextConfiguration(…)
: 設定したいApplicationContext
リソースを指定するためのアノテーション。
v4.1から、@SpringBatchTest
を使用するテストコンテキストでJobLauncherTestUtils
とJobRepositoryTestUtils
などSpring Batchのテストユーティリティをインジェクション可能になりました。
以下はアノテーションの使用例です。
Using Java Configuration
@SpringBatchTest @RunWith(SpringRunner.class) @ContextConfiguration(classes=SkipSampleConfiguration.class) public class SkipSampleFunctionalTests { ... }
1.2. End-To-End Testing of Batch Jobs
'End To End'の定義はバッチジョブを最初から最後まで実行するテストです。テストコンディションのセットアップ・ジョブ実行・最終結果の検証をテストします。
以下はDBから読み込みフラットファイルに書き込むバッチジョブの例です。テストメソッドはまずテストデータをDBにセットアップします。CUSTOMERテーブルをクリアし、10レコードを新規追加します。それからテストはlaunchJob()
でJob
を起動します。launchJob()
はJobLauncherTestUtils
にあります。JobLauncherTestUtils
にはlaunchJob(JobParameters)
もあり、テストにパラメータを指定できます。launchJob()
はJobExecution
を返し、Job
実行に関する情報をassertするなどに使います。以下の例では、テストでJob
が"COMPLETED"で終了することを検証しています。
Java Based Configuration
@SpringBatchTest @RunWith(SpringRunner.class) @ContextConfiguration(classes=SkipSampleConfiguration.class) public class SkipSampleFunctionalTests { @Autowired private JobLauncherTestUtils jobLauncherTestUtils; private SimpleJdbcTemplate simpleJdbcTemplate; @Autowired public void setDataSource(DataSource dataSource) { this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource); } @Test public void testJob() throws Exception { simpleJdbcTemplate.update("delete from CUSTOMER"); for (int i = 1; i <= 10; i++) { simpleJdbcTemplate.update("insert into CUSTOMER values (?, 0, ?, 100000)", i, "customer" + i); } JobExecution jobExecution = jobLauncherTestUtils.launchJob(); Assert.assertEquals("COMPLETED", jobExecution.getExitStatus().getExitCode()); } }
1.3. Testing Individual Steps
複雑なバッチジョブでは、end-to-endのテストケースは扱い辛くなりがちです。この場合、stepごとにテストケースを作る方が良い場合があります。JobLauncherTestUtils
にはlaunchStep
があり、これはstep名でそのStep
を実行します。これにより、特定step用のテストデータのセットアップをしてテスト実行をし、step実行結果を直接検証できます。以下はstep名でロードするのにlaunchStep
を使用する例です。
JobExecution jobExecution = jobLauncherTestUtils.launchStep("loadFileStep");
1.4. Testing Step-Scoped Components
stepに設定するコンポーネントが実行時にstepスコープと遅延バインディングでstepやjobのコンテキストをインジェクトする場合があります。これをスタンドアローンのコンポーネントとしてテストするのはトリッキーで、そのコンポーネントをstepに配置した状態を再現してコンテキストを設定する方法が必要です。このためにSpring BatchではStepScopeTestExecutionListener
とStepScopeTestUtils
を用意しています。
このリスナーをクラスレベルに宣言すると、jobはstepコンテキストをテストメソッドごとに生成します。以下が例です。
@ContextConfiguration @TestExecutionListeners( { DependencyInjectionTestExecutionListener.class, StepScopeTestExecutionListener.class }) @RunWith(SpringRunner.class) public class StepScopeTestExecutionListenerIntegrationTests { // このコンポーネントはstepスコープで定義しており、stepがアクティブでないと // インジェクトできない。 @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() { // readerを初期化して入力データをバインド assertNotNull(reader.read()); } }
ここには2つのTestExecutionListeners
があります。1つはSpring Testフレームワークのもので、設定したアプリケーションコンテキストからreaderのDIを処理するものです。もう1つはStepScopeTestExecutionListener
です。仕組みとしては、実行時にStep
がアクティブかのように振る舞うように、StepExecution
のテストケースでファクトリメソッドを参照します。ファクトリメソッドはシグネチャ(StepExecution
を返す)で検出されます。ファクトリメソッドが無い場合、デフォルトのStepExecution
を生成します。
v4.1からは、@SpringBatchTest
がある場合、StepScopeTestExecutionListener
とJobScopeTestExecutionListener
をテストクラスにtest execution listenersとしてインポートします。上の例は以下のようにも書けます。
@SpringBatchTest @RunWith(SpringRunner.class) @ContextConfiguration public class StepScopeTestExecutionListenerIntegrationTests { // このコンポーネントはstepスコープで定義しており、stepがアクティブでないと // インジェクトできない。 @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() { // readerを初期化して入力データをバインド assertNotNull(reader.read()); } }
テストメソッド実行をstepスコープに入れたい場合にリスナーを使う方法は手軽です。柔軟ではあるものの美しくない方法に、StepScopeTestUtils
を使うものがあります。以下の例は前述の例のreaderでアイテム数をカウントしています。
int count = StepScopeTestUtils.doInStepScope(stepExecution, new Callable<Integer>() { public Integer call() throws Exception { int count = 0; while (reader.read() != null) { count++; } return count; } });
1.5. Validating Output Files
バッチジョブがDB書き込みする場合、その出力が期待通りか検証するのはDBにクエリを投げるだけです。しかし、バッチジョブがファイルに書き込む場合も、同じく、出力の検証は重要です。Spring Batchには出力ファイルの検証を行うAssertFile
があります。assertFileEquals
は2つのFile
(もしくは2つのResource
)を取り、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));
1.6. Mocking Domain Objects
Spring Batchのユニットおよびインテグレーションテストの作成でよくあるもう一つの課題はドメインオブジェクトのモックです。好例は、以下コードのような、StepExecutionListener
です。
public class NoWorkFoundStepExecutionListener extends StepExecutionListenerSupport { public ExitStatus afterStep(StepExecution stepExecution) { if (stepExecution.getReadCount() == 0) { return ExitStatus.FAILED; } return null; } }
上のリスナーの例は、何も処理しなかったことを意味するreadカウントがゼロを、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
は正しくStepExecution
を生成するためにJobParameters
とJobInstance
を必要とします。堅実なドメインモデルですが、ユニットテスト用のスタブオブジェクト生成は冗長です。このため、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にあります。