Automating EJB Unit Testing
Pages: 1, 2, 3
Data-Driven Testing Implementation
It is always a good idea to use static final classes to contain the fixed
data instead of hard-coding them in the program. In ACL, we create a
non-instantiable utility class, TestData, to serve this
purpose.
public class TestData {
private TestData() {} // prevent instantiation
/**
* test data for Group
**/
// data to be used in code
public static final String GROUP_1 = "GROUP_1";
public static final String GROUP_2 = "GROUP_2";
public static final String GROUP_3 = "GROUP_3";
// initial data in the database
public static final String ADMIN_GROUP = "AdminGroup";
public static final String DEV_GROUP = "DevGroup";
public static final String OUT_GROUP = "OutGroup";
// ...
}
Notice we have put in both database initial data and test program data to make the maintenance of those data easier.
To extend this solution and make it more flexible, you can put the test data in XML files and read them at runtime. JXUnit does exactly this. There are other techniques you can use as well and they are covered under the topic of Data-Driven Testing.
Verify the Result
Visual inspection is a manual process; it breaks automation, so we have to
fix it. There are wide variety of assert methods in JUnit for us to use to
validate the test results for EJBs. For example, after creating a new entity
bean, assertion functions can be used to check the identicality of the data in
the database. We can retrieve the data from the database with simple SQL
statements, comparing it to the test data. In practice, a well-designed
JDBCFramework (see Resources) can be a lot of help in
executing SQL statements. The following code snippet creates a new
Group and retrieves the data with JDBCFramework before comparing
it to our test data.
public void testCreateGroup() throws Exception {
try {
groupHome.create(TestData.GROUP_1, TestData.GROUP_1);
System.out.println(TestData.GROUP_1 + " created");
// verify the result
GroupDTO group = GroupSqlHelper.getGroup(TestData.GROUP_1);
assertEquals(group.getName(), TestData.GROUP_1);
} catch (Exception e) {
// clean the db in case of error, e.g. duplicated key
GroupSqlHelper.deleteGroup(TestData.GROUP_1);
}
}
Generally, the test code does not need to catch application exceptions. JUnit will automatically consider uncaught exceptions as errors. In the above sample, we catch the exception to make sure the database is clean after executing the test program.
The JDBCFramework is not only useful in the unit testing, but actually imperative in EJB development. Using straight JDBC instead of entity beans for bulk reading and deleting is a common practice in EJB development. For example, it is better to use direct JDBC calls to delete all records in the database instead of finding all of the entity beans to remove them one by one.
public void deleteAllGroups() throws Exception {
//implemented by JDBCFramework
GroupSqlHelper.deleteAllGroups();
}
Leave Nothing
A unit test should always restore the original state of the system after it finishes -- the state of the system should be exactly the same before and after you run your unit test. This makes sure any of the test programs only depend on and solely rely on the initial state (data) of the testing system. Changing the system state in one test program in the middle could make other tests fail, preventing a successful automation.
When it comes to the actual implementation, in case of errors during testing, your test code should make sure that the system state is predictable. That is, you must clean up after yourself when your test finishes or crashes. However the test code is run, the database must be clean to allow subsequent running of other tests. The database must be restored to the initial state before the next test is run. This means at least two things:
- If the test crashes, all of the data created so far must be deleted.
- All data that has been deleted or modified must be restored to initial values.
The JDBCFramework can come to our rescue again. For example, the following
code snippet calls the deleteGroup() method to delete the record
in case the EJB remove test code fails. If you are modifying a record, an
update method can help to restore the initial database values.
public void testDeleteGroupByGroupId() throws Exception {
try {
GroupKey key1 = new GroupKey(TestData.GROUP_1);
groupHome.remove(key1);
} catch (Exception e) {
// do a manatory remove to make sure db is clean
GroupSqlHelper.deleteGroup(TestData.GROUP_1);
}
assertNull(GroupSqlHelper.getGroup(TestData.GROUP_1));
}
Unit Testing Session Beans
Session beans are used more or less as façades in EJB systems. A
session bean can implement a collection manager pattern such as
UserManager for entity bean User,
GroupManager for Group, or a façade to a
subsystem that provides business logic, such as ACLManager for
ACL.
In the first case, all methods in the session bean are delegated to corresponding entity beans so that the tests applied to the entity bean apply to the session bean as well.
In the second case, if the methods in the session bean modify the database
(and you can tell this, right?) the rules that apply to the entity bean should
apply as well. But if the particular method you are testing is not modifying
the database; e.g., the isPrivilegeGranted() method in
ACLManager, then you can ignore those rules. Most of the time, you
just need to be sure the data the method relies on is already in the database.
Of course, you should have the proper way to verify the result as well.
Conclusion
We have looked for a better way to test our EJB and web applications for a long time. We have seen many ideas and debates on this topic, such as how many databases are required, which framework is better, etc. Instead of finding a perfect solution, we tried to simplify the problem. The simplest solution could possibly work.
Resources
Test Infect your Enterprise JavaBeans, by Michael T. Nygard and Tracie Karsjens
Developing and Unit Testing with Open Source Apache Cactus Framework Tools in WebSphere Studio Application Developer, by Sheldon Wosnick, IBM WebSphere Developer Technical Journal
The Art of Objects, Object-Oriented Design and Architecture, by Yun-Tung Lau, Addison-Wesley, 2000.
Pattern your way to automated regression testing, by Kevin Pauli
Eliminate JDBC overhead, by Ryan Daigle, contains the author's JDBCFramework