Home > Development > features
Anatomy of an Enterprise Flex RIA Part 11: Testing What We Have
I’ve already introduced you to the concept of testing frameworks. Let’s create a few unit tests to see that the entities we’ve created get into and out of the database in the way we want them to.
TestNG
Maven works well out of the box with two Java testing frameworks: JUnit and TestNG. TestNG is not as well known as JUnit, but it’s a nice framework with a few interesting features. One of these is the ability to group tests. Maven works with TestNG by telling the POM where to find a TestNG configuration file.
This ability to group tests together allows us to run certain tests at certain times, or exclude certain groups if we want.
Here’s the configuration file for TestNG:
<suite name="Bookie All Tests" verbose="5">
<test name="Unit Tests">
<groups>
<run>
<include name="unit.*" />
<exclude name="integration.*" />
</run>
</groups>
<packages>
<package name="lcds.examples.bookie.*" />
</packages>
</test>
<test name="Integration Tests">
<groups>
<run>
<include name="integration.*" />
</run>
</groups>
<packages>
<package name="lcds.examples.bookie.*" />
</packages>
</test>
</suite>
We can include tests in a group by package and by name. Names are given in the actual classes, and we’ll see them in a bit. Here are the relevant parts of the POM for the data project that tells Maven where to find the classes to test:
...
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<suiteXmlFiles>
<suiteXmlFile>
${basedir}/src/test/resources/testng.xml
</suiteXmlFile>
</suiteXmlFiles>
<parallel>true</parallel>
<trimStackTrace>false</trimStackTrace>
<systemProperties>
<property>
<name>java.class.path</name>
<value>target/classes</value>
</property>
</systemProperties>
</configuration>
</plugin>
...
The Maven surefire plug-in manages testing. We tell it where to find the TestNG configuration file, to run the tests in parallel (which is faster), to cut out unimportant parts of the stack trace if there’s an error, and that we want all the classes in target/classes added to the classpath.
DBUnit
DBUnit is a framework that allows us to “seed” the database with a set of data before each test. Here’s an example from bookie-data/src/test/resources/fds/examples/
bookie/datasets/searchable_books_dataset.xml:
<?xml version='1.0' encoding='UTF-8'?>
<dataset>
<subjects id="1" name="Astrophysics" />
<subjects id="2" name="Roman Literature" />
<subjects id="3" name="Psychoacoustics" />
<subjects id="4" name="Zymurgy" />
<people id="1" first_name="User"
last_name="Man" author="false" card_number="123456" />
<people id="2" first_name="Author"
last_name="Conan-Doyle" author="true" />
<people id="3" first_name="Charlie"
last_name="Papazian" author="true" />
<people id="4" first_name="Marcus"
last_name="Cicero" author="true" />
<people id="5" first_name="Fred"
last_name="Author" author="true" />
<books id="1" author_id="2"
title="Space" subject_id="1" />
<books id="2" author_id="2"
title="More on Space" subject_id="3" />
<books id="3" author_id="4"
title="De Oratore" subject_id="2" />
<books id="4" author_id="4"
title="De Legibus" subject_id="2" />
<books id="5" author_id="5"
title="Now You Hear It, Now You Don't" subject_id="3" />
<books id="6" author_id="3"
title="The Complete Joy of Homebrewing" subject_id="4" />
<books id="7" author_id="3"
title="The Homebrewer's Companion" subject_id="4" />
</dataset>
Inside the dataset root tag, each tag corresponds to a row in the table named by the tag’s name. You can see that we’re creating four subjects, five people, and seven books, and there are some relationships set up from books to subjects and authors. One of the people is a user, with a card_number. This is a nice little set of data to test a few important things: searching for a book by subject or author, searching for authors or subjects, or logging in as a user. We’ll see how we get this data into the database next.
The Tests
Let’s separate the tests into two types: testing simple Create, Update, and Delete (abbreviated CRUD), and then tests that exercise the parts of the persistence layer that support the user stories.
First, though, our tests will extend a base test, BaseTest:
package lcds.examples.bookie;
...
@Test(groups="unit")
public class BaseTest extends AssertJUnit {
}
package lcds.examples.bookie;
...
@Test(groups="functional")
public abstract class BaseEJBTest extends BaseTest {
private static EJB3StandaloneDeployer deployer;
private static ConnectionHelper connectionHelper;
private static DatabaseConnection connection;
@BeforeSuite
public static void startEmbeddedJBoss() throws Exception {
EJB3StandaloneBootstrap.ignoredJars.add("rt.jar");
EJB3StandaloneBootstrap.boot(null);
deployer = EJB3StandaloneBootstrap.createDeployer();
deployer.getArchivesByResource().add("META-INF/persistence.xml");
deployer.create();
deployer.start();
// Configure the DAOLookupHelper
DAOLookupHelper.getInstance().setJNDIPrefix("");
}
@BeforeSuite
public static void setUpConnection() throws Exception {
connectionHelper = new ConnectionHelper();
}
First, we put the tests in the “functional” group. This overrides the “unit” group in the BaseTest class, so all EJB tests will be part of the “functional” group. We don’t really have any unit tests; I just put that there so that you can see how to group tests in a way that makes sense. Next, we use the @BeforeSuite annotation to tell TestNG to run startEmbeddedJBoss before any test in this test suite. Embedded JBoss is included with the project so that we don’t have to run tests inside the JBoss container to get access to the important services that JBoss provides. It’s a real timesaver. Basically, startEmbeddedJBoss does a few setup tasks, and then creates a deployer. The deployer will deploy our persistence unit by looking for any archives with a META-INF/persistence.xml in them. Then the deployer is started.
DAOLookupHelper is just a simple class I made to assist with a simple problem with JNDI in embedded JBoss and real JBoss. Embedded JBoss provides us with one JNDI context, which is where we need to look up session beans. In JBoss the server, however, the name of your EAR file is appended onto the front of the JNDI context. The lookup helper just keeps track of that (make sure you change it if you change the name of the EAR).
The next @BeforeSuite method, setUpConnection, uses a simple helper class to manage getting database connections for DBUnit in the tests. Constructing a connectionHelper uses a properties file in the test/resources directory under the package structure to store the database connection properties.
@AfterSuite
public static void stopEmbeddedJBoss() {
try {
if (deployer != null) {
deployer.stop();
deployer.destroy();
}
EJB3StandaloneBootstrap.shutdown();
} catch (Exception e) {
System.out.println("Error shutting down embedded JBoss after tests: " + e.toString());
}
}
@AfterSuite
protected void tearDownDatabase() throws Exception {
if (connection != null) {
connection.close();
}
}
As you’d expect, the @AfterSuite annotated methods are run after the suite is over, so this is where we can do some cleanup after all the tests are done.
First, we’ll stop the embedded JBoss instance in stopEmbeddedJBoss, and we’ll close the database connection in tearDownDatabase:
protected static DatabaseConnection getDatabaseConnection()
throws Exception {
if (connection == null) {
connection = new DatabaseConnection(connectionHelper.getConnection());
}
return connection;
}
protected Object lookup(String name) throws Exception {
Hashtable<String, String> props =
new Hashtable<String, String>();
props.put(
"java.naming.factory.url.pkgs",
"org.jboss.naming:org.jnp.interfaces");
props.put(
"java.naming.factory.initial",
"org.jnp.interfaces.LocalOnlyContextFactory");
InitialContext ic = new InitialContext(props);
return ic.lookup(name);
}
}
Then there are a few helper methods: getDatabaseConnection gets a connection from the connectionHelper, and lookup assists in looking up anything from JNDI using the right settings for embedded JBoss. Here’s an example test that simply tests the processes of creating and updating:
package lcds.examples.bookie.entity;
...
public class TestCrud extends BaseEJBTest {
@Test
public void testCRUDSubject() throws Exception {
Subject s = new Subject();
s.setName("Zymurgy");
SubjectDAO dao = DAOLookupHelper.getInstance().getSubjectDAO();
s = dao.merge(s);
Integer id = s.getId();
assertNotNull(dao.findById(id));
s.setName("Beer Making");
dao.merge(s);
assertEquals("Expected Zymurgy to be updated to Beer Making", "Beer Making", dao.findById(id).getName());
dao.remove(s);
assertNull(dao.findById(id));
}
and a few more tests after that. This test is typical, though, and it shows how we can create, update, and delete an entity using a session bean. Look in the test for a few more examples. Here’s an example test that uses the search-type features of the session beans:
package lcds.examples.bookie.entity;
...
public class TestBrowsing extends BaseEJBTest {
...
@BeforeClass
public void setup() throws Exception {
connection = getDatabaseConnection();
URL dsPath =
this.getClass().getResource(
"/fds/examples/bookie/datasets/searchable_books_dataset.xml");
searchableBooksDataset =
new FlatXmlDataSet(
new FileInputStream(dsPath.getFile()));
DatabaseOperation.CLEAN_INSERT.execute(
connection, searchableBooksDataset);
}
@Test
public void testSignIn() throws Exception {
PersonDAO dao =
DAOLookupHelper.getInstance().getPersonDAO();
int user_id =
Integer.parseInt(
(String)searchableBooksDataset.getTable("people").
getValue(0, "id"));
Person user = dao.findById(user_id);
String cardNumber =
(String)searchableBooksDataset.getTable("people").
getValue(0, "card_number");
assertEquals(user.getCardNumber(), cardNumber);
Person cardHolder = dao.findByCardNumber(cardNumber);
assertEquals(user, cardHolder);
}
...
A new annotation, @BeforeClass, tells TestNG to run the annotated method before every test in the class. The setup method gets a connection from the getDataBaseConnection base classes. Then it gets a reference to the data set we talked about a few pages back. It passes this URL into a FlatXmlDataSet constructor, which is part of DBUnit, and then uses that object to pass to a DatabaseOperation of type CLEAN_INSERT. A clean insert first truncates the tables referenced in the data set, and then inserts the data from the data set. Because the setup method is executed once per test, we can be sure that the database is in a certain state every test.
The testSignIn method is an example of a test that uses data from a data set. First it looks up the ID of the first row in the data set. Then it finds a person with that ID and gets that person’s card number from the data set. It ensures that the card number is the same as the card number of the person it looked up, and then it looks up a person by that card number and makes sure it found the person it found earlier.
Figure 14 shows where to find the classes and resources used in the tests we’ve gone over. There are a lot of files in src/test/resources that have to do with configuring embedded JBoss. Have a look if you like, but otherwise, just let them do what they do.
These tests are fairly simple, but keep in mind that any untested code is a surprise waiting to happen. It’s like the high school student who worried and worried about an upcoming test and so studied every night. When the test came and he aced it, he said, “I wish I wouldn’t have wasted all that time studying; that test was easy!” Having a bunch of simple passing tests isn’t a waste of time writing tests; it’s coverage. Changes to the code can break things in simple places as well as complex ones.
Enough preaching! Let’s look at building out the service layer so that we can get another step closer to getting these entities from Java to Flex.
Next time we're going to look at the Maven build for our service layer and be introduced to the concept of Live Cycle Data Services' assemblers. You can always find the entire series here.



Comments
Leave a comment