ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

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