|
|
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 4 of 5
An integration specification tests several application components working together. Integration testing is complicated by
the fact that Java EE application components often have external dependencies such as javax.sql.DataSource, javax.mail.Session, javax.jms.Queue, javax.jms.Topic, javax.transaction.TransactionManager, and many others.
In order to keep application source code as maintainable as possible, and to remove as much complexity from the build process as possible, we want to keep the source code that is running on the development machines in sync with the source code that is deployed on the pre-production and production servers.
Each machine will of course have its own RDBMS and JMS configuration, and some details of the configuration likely are (and should be) inaccessible to us as developers. For instance, the RDBMS configuration for the production machine should not be left to developers.
If we were testing the Spring way, we'd use the spring-test artifact with the @ContextConfiguration annotation on a JUnit test. Spring would then construct the components under test by examining the Spring configuration files.
The spring-test would leave us to set up the JNDI environment, a non-trivial and repetitive task. So we have two challenges: first, to bring
the equivalent of spring-test to Scala and Specs2, then to set up the JNDI environment for the test.
Java allows us to register resources in JNDI; these resources could include a JDBC DataSource, a JMS ConnectionFactory, a Queue, a JavaMail Session, and so on. While it's a simplification, you could think of JNDI as a Map<String, Object> (or, speaking in Scala: Map[String, AnyRef]). Essentially, the application consumes objects from JNDI and uses them in its code. In Spring applications, the consumed
JNDI resources will most likely become beans in the Spring ApplicationContext.
For further details of the JNDI and Spring setup code, see the Specs2 Spring documentation from Cake Solutions.
In addition to test code we need to include code that sets up JNDI for the test. The JDNI code should be reusable, and it
also must run prior to other code in the test case. We see this in Listing 4, where the specification's setup code prepares
the JNDI environment, then adds the Spring ApplicationContext, and finally injects the required beans into the specification, as shown in Listing 4.
As developers, we code the @DataSource, @TransactionManager, and @ContextConfiguration annotations. Specs2 Spring then sets up the JNDI environment for the test and builds the Spring ApplicationContext with the components under test. Finally, it injects the instances of the components from the ApplicationContext into the test. (Note @Autowired var riderManager: RiderManager = _ in Listing 4.)
@DataSource(name = "java:comp/env/jdbc/test",
driverClass = classOf[JDBCDriver], url = "jdbc:hsqldb:mem:test")
@TransactionManager(name = "java:comp/TransactionManager")
@ContextConfiguration(Array("classpath*:/META-INF/spring/module-context.xml"))
class RiderManagerSpec extends Specification {
@Autowired var riderManager: RiderManager = _
"Generate 10 riders" in {
val count = 10
this.riderManager.generate(count)
this.riderManager.findAll.size() must be_==(count)
}
}
The Specs2 Spring code in Listing 4 handled much of our test setup with very little code. The the body of the specification
("Generate 10 riders") also leveraged some helpful features of Specs2. Specs2 Spring prepared the JNDI environment for the test and instantiated
the Spring ApplicationContext, then it injected the RiderManager instance into the RiderManagerSpec.
Listing 5 shows the testing portion of code from Listing 4. Note that the Specs2 code, written in Scala, is shorter and clearer than Java code, while still expressing the same test.
...
class RiderManagerSpec extends Specification {
...
"Generate 10 riders" in {
val count = 10
this.riderManager.generate(count)
this.riderManager.findAll.size() must be_==(count)
}
}
The "Generate 10 riders" specification's body clearly expresses what is being tested with as little syntactic noise as possible. Instead of Java's
cumbersome Assert.assertThat(this.riderManager.findAll.size(), CoreMatchers.is(count)), Scala gave us: this.riderManager.findAll.size() must be_==(count). (Using static imports in Java would be another way to get at more elegant syntax, but I feel that it is still far too verbose;
e.g., assertThat(this.riderManager.findAll.size(), is(count)).)
In some cases you will want to run more than one test (or example in Specs2) in a specification. It should be possible to run the examples in parallel, in multiple threads. So, suppose that
we have two examples in our RiderManagerSpec, and that both of them would modify the database. If we run both examples in parallel, it will be difficult to ensure that
they do not influence each other. We don't want to lose the parallel nature of Specs2, but we do want to isolate every test
example. This is a problem for transactional semantics!
It is easy to modify the specification to run every example in its transaction and then roll back that transaction when the example finishes, as shown in Listing 6.
@Transactional
@DataSource(name = "java:comp/env/jdbc/test",
driverClass = classOf[JDBCDriver], url = "jdbc:hsqldb:mem:test")
@TransactionManager(name = "java:comp/TransactionManager")
@ContextConfiguration(Array("classpath*:/META-INF/spring/module-context.xml"))
class RiderManagerSpec extends Specification {
...
}
Notice the @Transactional annotation on the specification. When Specs2 Spring sees this annotation, it will look for a PlatformTransactionManager bean in the Spring context constructed for the test. It will then use that PlatformTransactionManager to wrap the example in a transaction. It will automatically roll back the completed transaction, regardless of whether it
completed successfully or failed.
Now let's take a quick look at what Specs2 Spring does to simplify writing acceptance specifications.
Version 1.0 of Specs2 Spring will support acceptance testing of servlet-based applications. This will include web applications
that interact with browsers or other systems and use DispatcherServlet or the MessageDispatcherServlet. Specs2 Spring Web will enable developers to write specifications that can verify the behavior of the entire system, including
its interface. At the same time, it will let specifications access the underlying structure of the application. Listing 7
shows an acceptance specification in Specs2 Spring.