194 lines
18 KiB
HTML
194 lines
18 KiB
HTML
<html><head>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
|
<title>10. Unit Testing</title><link rel="stylesheet" type="text/css" href="css/manual-multipage.css"><meta name="generator" content="DocBook XSL Stylesheets V1.78.1"><link rel="home" href="index.html" title="Spring Batch - Reference Documentation"><link rel="up" href="index.html" title="Spring Batch - Reference Documentation"><link rel="prev" href="retry.html" title="9. Retry"><link rel="next" href="patterns.html" title="11. Common Batch Patterns"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">10. Unit Testing</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="retry.html">Prev</a> </td><th width="60%" align="center"> </th><td width="20%" align="right"> <a accesskey="n" href="patterns.html">Next</a></td></tr></table><hr></div><div class="chapter"><div class="titlepage"><div><div><h1 class="title"><a name="testing" href="#testing"></a>10. Unit Testing</h1></div></div></div><p>Just as with other application styles, it is extremely important to
|
|
unit test any code written as part of a batch job as well. The Spring core
|
|
documentation covers how to unit and integration test with Spring in great
|
|
detail, so it won't be repeated here. It is important, however, to think
|
|
about how to 'end to end' test a batch job, which is what this chapter will
|
|
focus on. The spring-batch-test project includes classes that will help
|
|
facilitate this end-to-end test approach.</p><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="creatingUnitTestClass" href="#creatingUnitTestClass"></a>10.1 Creating a Unit Test Class</h2></div></div></div><p>In order for the unit test to run a batch job, the framework must
|
|
load the job's ApplicationContext. Two annotations are used to trigger
|
|
this:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p><code class="classname">@RunWith(SpringJUnit4ClassRunner.class)</code>:
|
|
Indicates that the class should use Spring's JUnit facilities</p></li><li class="listitem"><p><code class="classname">@ContextConfiguration(locations = {...})</code>:
|
|
Indicates which XML files contain the ApplicationContext.</p></li></ul></div><pre class="programlisting"><em><span class="hl-annotation" style="color: gray">@RunWith(SpringJUnit4ClassRunner.class)</span></em>
|
|
<em><span class="hl-annotation" style="color: gray">@ContextConfiguration(locations = { "/simple-job-launcher-context.xml",
|
|
"/jobs/skipSampleJob.xml" })</span></em>
|
|
<span class="hl-keyword">public</span> <span class="hl-keyword">class</span> SkipSampleFunctionalTests { ... }</pre></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="endToEndTesting" href="#endToEndTesting"></a>10.2 End-To-End Testing of Batch Jobs</h2></div></div></div><p>'End To End' testing can be defined as testing the complete run of a
|
|
batch job from beginning to end. This allows for a test that sets up a
|
|
test condition, executes the job, and verifies the end result.</p><p>In the example below, the batch job reads from the database and
|
|
writes to a flat file. The test method begins by setting up the database
|
|
with test data. It clears the CUSTOMER table and then inserts 10 new
|
|
records. The test then launches the <code class="classname">Job </code>using the
|
|
<code class="methodname">launchJob()</code> method. The
|
|
<code class="methodname">launchJob</code>() method is provided by the
|
|
<code class="classname">JobLauncherTestUtils</code> class. Also provided by the
|
|
utils class is <code class="classname">launchJob(JobParameters)</code>, which
|
|
allows the test to give particular parameters. The
|
|
<code class="methodname">launchJob()</code> method returns the
|
|
<code class="classname">JobExecution</code> object which is useful for asserting
|
|
particular information about the <code class="classname">Job</code> run. In the
|
|
case below, the test verifies that the <code class="classname">Job</code> ended
|
|
with status "COMPLETED".</p><pre class="programlisting"><em><span class="hl-annotation" style="color: gray">@RunWith(SpringJUnit4ClassRunner.class)</span></em>
|
|
<em><span class="hl-annotation" style="color: gray">@ContextConfiguration(locations = { "/simple-job-launcher-context.xml",
|
|
"/jobs/skipSampleJob.xml" })</span></em>
|
|
<span class="hl-keyword">public</span> <span class="hl-keyword">class</span> SkipSampleFunctionalTests {
|
|
|
|
<em><span class="hl-annotation" style="color: gray">@Autowired</span></em>
|
|
<span class="hl-keyword">private</span> JobLauncherTestUtils jobLauncherTestUtils;
|
|
|
|
<span class="hl-keyword">private</span> SimpleJdbcTemplate simpleJdbcTemplate;
|
|
|
|
<em><span class="hl-annotation" style="color: gray">@Autowired</span></em>
|
|
<span class="hl-keyword">public</span> <span class="hl-keyword">void</span> setDataSource(DataSource dataSource) {
|
|
<span class="hl-keyword">this</span>.simpleJdbcTemplate = <span class="hl-keyword">new</span> SimpleJdbcTemplate(dataSource);
|
|
}
|
|
|
|
<em><span class="hl-annotation" style="color: gray">@Test</span></em>
|
|
<span class="hl-keyword">public</span> <span class="hl-keyword">void</span> testJob() <span class="hl-keyword">throws</span> Exception {
|
|
simpleJdbcTemplate.update(<span class="hl-string">"delete from CUSTOMER"</span>);
|
|
<span class="hl-keyword">for</span> (<span class="hl-keyword">int</span> i = <span class="hl-number">1</span>; i <= <span class="hl-number">10</span>; i++) {
|
|
simpleJdbcTemplate.update(<span class="hl-string">"insert into CUSTOMER values (?, 0, ?, 100000)"</span>,
|
|
i, <span class="hl-string">"customer"</span> + i);
|
|
}
|
|
|
|
JobExecution jobExecution = jobLauncherTestUtils.launchJob().getStatus();
|
|
|
|
|
|
Assert.assertEquals(<span class="hl-string">"COMPLETED"</span>, jobExecution.getExitStatus());
|
|
}
|
|
}</pre></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="testingIndividualSteps" href="#testingIndividualSteps"></a>10.3 Testing Individual Steps</h2></div></div></div><p>For complex batch jobs, test cases in the end-to-end testing
|
|
approach may become unmanageable. It these cases, it may be more useful to
|
|
have test cases to test individual steps on their own. The
|
|
<code class="classname">AbstractJobTests</code> class contains a method
|
|
<code class="methodname">launchStep</code> that takes a step name and runs just
|
|
that particular <code class="classname">Step</code>. This approach allows for more
|
|
targeted tests by allowing the test to set up data for just that step and
|
|
to validate its results directly.</p><pre class="programlisting">JobExecution jobExecution = jobLauncherTestUtils.launchStep(<span class="hl-string">"loadFileStep"</span>);</pre></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="d5e3514" href="#d5e3514"></a>10.4 Testing Step-Scoped Components</h2></div></div></div><p>Often the components that are configured for your steps at runtime
|
|
use step scope and late binding to inject context from the step or job
|
|
execution. These are tricky to test as standalone components unless you
|
|
have a way to set the context as if they were in a step execution. That is
|
|
the goal of two components in Spring Batch: the
|
|
<code class="classname">StepScopeTestExecutionListener</code> and the
|
|
<code class="classname">StepScopeTestUtils</code>.</p><p>The listener is declared at the class level, and its job is to
|
|
create a step execution context for each test method. For example:</p><pre class="programlisting"><em><span class="hl-annotation" style="color: gray">@ContextConfiguration</span></em>
|
|
<em><span class="hl-annotation" style="color: gray">@TestExecutionListeners( { DependencyInjectionTestExecutionListener.class,
|
|
StepScopeTestExecutionListener.class })</span></em>
|
|
<em><span class="hl-annotation" style="color: gray">@RunWith(SpringJUnit4ClassRunner.class)</span></em>
|
|
<span class="hl-keyword">public</span> <span class="hl-keyword">class</span> StepScopeTestExecutionListenerIntegrationTests {
|
|
|
|
<span class="hl-comment">// This component is defined step-scoped, so it cannot be injected unless</span>
|
|
<span class="hl-comment">// a step is active...</span>
|
|
<em><span class="hl-annotation" style="color: gray">@Autowired</span></em>
|
|
<span class="hl-keyword">private</span> ItemReader<String> reader;
|
|
|
|
<span class="hl-keyword">public</span> StepExecution getStepExection() {
|
|
StepExecution execution = MetaDataInstanceFactory.createStepExecution();
|
|
execution.getExecutionContext().putString(<span class="hl-string">"input.data"</span>, <span class="hl-string">"foo,bar,spam"</span>);
|
|
<span class="hl-keyword">return</span> execution;
|
|
}
|
|
|
|
<em><span class="hl-annotation" style="color: gray">@Test</span></em>
|
|
<span class="hl-keyword">public</span> <span class="hl-keyword">void</span> testReader() {
|
|
<span class="hl-comment">// The reader is initialized and bound to the input data</span>
|
|
assertNotNull(reader.read());
|
|
}
|
|
|
|
}</pre><p>There are two <code class="classname">TestExecutionListeners</code>, one
|
|
from the regular Spring Test framework and handles dependency injection
|
|
from the configured application context, injecting the reader, and the
|
|
other is the Spring Batch
|
|
<code class="classname">StepScopeTestExecutionListener</code>. It works by looking
|
|
for a factory method in the test case for a
|
|
<code class="classname">StepExecution</code>, and using that as the context for
|
|
the test method, as if that execution was active in a Step at runtime. The
|
|
factory method is detected by its signature (it just has to return a
|
|
<code class="classname">StepExecution</code>). If a factory method is not provided
|
|
then a default <code class="classname">StepExecution</code> is created.</p><p>The listener approach is convenient if you want the duration of the
|
|
step scope to be the execution of the test method. For a more flexible,
|
|
but more invasive approach you can use the
|
|
<code class="classname">StepScopeTestUtils</code>. For example, to count the
|
|
number of items available in the reader above:</p><pre class="programlisting"><span class="hl-keyword">int</span> count = StepScopeTestUtils.doInStepScope(stepExecution,
|
|
<span class="hl-keyword">new</span> Callable<Integer>() {
|
|
<span class="hl-keyword">public</span> Integer call() <span class="hl-keyword">throws</span> Exception {
|
|
|
|
<span class="hl-keyword">int</span> count = <span class="hl-number">0</span>;
|
|
|
|
<span class="hl-keyword">while</span> (reader.read() != null) {
|
|
count++;
|
|
}
|
|
<span class="hl-keyword">return</span> count;
|
|
}
|
|
});</pre></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="validatingOutputFiles" href="#validatingOutputFiles"></a>10.5 Validating Output Files</h2></div></div></div><p>When a batch job writes to the database, it is easy to query the
|
|
database to verify that the output is as expected. However, if the batch
|
|
job writes to a file, it is equally important that the output be verified.
|
|
Spring Batch provides a class <code class="classname">AssertFile</code> to
|
|
facilitate the verification of output files. The method
|
|
<code class="methodname">assertFileEquals</code> takes two
|
|
<code class="classname">File</code> objects (or two
|
|
<code class="classname">Resource</code> objects) and asserts, line by line, that
|
|
the two files have the same content. Therefore, it is possible to create a
|
|
file with the expected output and to compare it to the actual
|
|
result:</p><pre class="programlisting"><span class="hl-keyword">private</span> <span class="hl-keyword">static</span> <span class="hl-keyword">final</span> String EXPECTED_FILE = <span class="hl-string">"src/main/resources/data/input.txt"</span>;
|
|
<span class="hl-keyword">private</span> <span class="hl-keyword">static</span> <span class="hl-keyword">final</span> String OUTPUT_FILE = <span class="hl-string">"target/test-outputs/output.txt"</span>;
|
|
|
|
AssertFile.assertFileEquals(<span class="hl-keyword">new</span> FileSystemResource(EXPECTED_FILE),
|
|
<span class="hl-keyword">new</span> FileSystemResource(OUTPUT_FILE));</pre></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="mockingDomainObjects" href="#mockingDomainObjects"></a>10.6 Mocking Domain Objects</h2></div></div></div><p>Another common issue encountered while writing unit and integration
|
|
tests for Spring Batch components is how to mock domain objects. A good
|
|
example is a <code class="classname">StepExecutionListener</code>, as illustrated
|
|
below:</p><pre class="programlisting"><span class="hl-keyword">public</span> <span class="hl-keyword">class</span> NoWorkFoundStepExecutionListener <span class="hl-keyword">extends</span> StepExecutionListenerSupport {
|
|
|
|
<span class="hl-keyword">public</span> ExitStatus afterStep(StepExecution stepExecution) {
|
|
<span class="hl-keyword">if</span> (stepExecution.getReadCount() == <span class="hl-number">0</span>) {
|
|
<span class="hl-keyword">throw</span> <span class="hl-keyword">new</span> NoWorkFoundException(<span class="hl-string">"Step has not processed any items"</span>);
|
|
}
|
|
<span class="hl-keyword">return</span> stepExecution.getExitStatus();
|
|
}
|
|
}</pre><p>The above listener is provided by the framework and checks a
|
|
<code class="classname">StepExecution</code> for an empty read count, thus
|
|
signifying that no work was done. While this example is fairly simple, it
|
|
serves to illustrate the types of problems that may be encountered when
|
|
attempting to unit test classes that implement interfaces requiring Spring
|
|
Batch domain objects. Consider the above listener's unit test:</p><pre class="programlisting"><span class="hl-keyword">private</span> NoWorkFoundStepExecutionListener tested = <span class="hl-keyword">new</span> NoWorkFoundStepExecutionListener();
|
|
|
|
<em><span class="hl-annotation" style="color: gray">@Test</span></em>
|
|
<span class="hl-keyword">public</span> <span class="hl-keyword">void</span> testAfterStep() {
|
|
<span class="bold"><strong>StepExecution stepExecution = new StepExecution("NoProcessingStep",
|
|
new JobExecution(new JobInstance(1L, new JobParameters(),
|
|
"NoProcessingJob")));</strong></span>
|
|
|
|
stepExecution.setReadCount(<span class="hl-number">0</span>);
|
|
|
|
<span class="hl-keyword">try</span> {
|
|
tested.afterStep(stepExecution);
|
|
fail();
|
|
} <span class="hl-keyword">catch</span> (NoWorkFoundException e) {
|
|
assertEquals(<span class="hl-string">"Step has not processed any items"</span>, e.getMessage());
|
|
}
|
|
}</pre><p>Because the Spring Batch domain model follows good object orientated
|
|
principles, the StepExecution requires a
|
|
<code class="classname">JobExecution</code>, which requires a
|
|
<code class="classname">JobInstance</code> and
|
|
<code class="classname">JobParameters</code> in order to create a valid
|
|
<code class="classname">StepExecution</code>. While this is good in a solid domain
|
|
model, it does make creating stub objects for unit testing verbose. To
|
|
address this issue, the Spring Batch test module includes a factory for
|
|
creating domain objects: <code class="classname">MetaDataInstanceFactory</code>.
|
|
Given this factory, the unit test can be updated to be more
|
|
concise:</p><pre class="programlisting"><span class="hl-keyword">private</span> NoWorkFoundStepExecutionListener tested = <span class="hl-keyword">new</span> NoWorkFoundStepExecutionListener();
|
|
|
|
<em><span class="hl-annotation" style="color: gray">@Test</span></em>
|
|
<span class="hl-keyword">public</span> <span class="hl-keyword">void</span> testAfterStep() {
|
|
<span class="bold"><strong>StepExecution stepExecution = MetaDataInstanceFactory.createStepExecution();</strong></span>
|
|
|
|
stepExecution.setReadCount(<span class="hl-number">0</span>);
|
|
|
|
<span class="hl-keyword">try</span> {
|
|
tested.afterStep(stepExecution);
|
|
fail();
|
|
} <span class="hl-keyword">catch</span> (NoWorkFoundException e) {
|
|
assertEquals(<span class="hl-string">"Step has not processed any items"</span>, e.getMessage());
|
|
}
|
|
}</pre><p>The above method for creating a simple
|
|
<code class="classname">StepExecution</code> is just one convenience method
|
|
available within the factory. A full method listing can be found in its
|
|
<a class="ulink" href="http://docs.spring.io/spring-batch/apidocs/org/springframework/batch/test/MetaDataInstanceFactory.html" target="_top">Javadoc</a>.</p></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="retry.html">Prev</a> </td><td width="20%" align="center"> </td><td width="40%" align="right"> <a accesskey="n" href="patterns.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">9. Retry </td><td width="20%" align="center"><a accesskey="h" href="index.html">Home</a></td><td width="40%" align="right" valign="top"> 11. Common Batch Patterns</td></tr></table></div></body></html> |