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

The ACL Sample

There is nothing more convincible in XP than running sample code. In this article, we choose the Access Control List sample from the book The Art of Objects (see Resources), since it has enough complexity to represent our ideas on EJB unit testing but is easy to understand. Here is a fine-grained access control system implemented by a binary association class pattern.




Figure 1

The class ACL is associated with multiple users and groups and has has associated classes UserAccess and GroupAccess. The Privilege class specifies the privilege for UserAccess or GroupAccess (such as read-only, read-write, and so on). A User can belong to more than one Group. Under an ACL object, there may be many users and groups. The ACL class will have the operation isPrivilegeGranted(user : User, requestedPrivilege : Privilege). Certain policies may be embedded in the operation. For example:

  • If a user appears in multiple groups, then pick the one with the highest privilege.
  • If a user appears as an individual in an ACL, then its privilege overwrites any settings in a group.

To implement the above model in EJB, we will have as entity beans User, Group, UserAccess, GroupAccess, ACL, and Privilege, and a session bean of ACLManager. Figure 2 shows a simple database ER diagram. You can find SQL scripts for DB2 in the attached source code to this article, as well as all of the Java source code in the EAR file generated from IBM WebSphere Studio Application Developer version 5.


Figure 2

Automating Entity Bean Unit Testing

To Be a True Unit Test

Unit testing is best kept at the class level. Generally, unit testing code should test only one EJB at a time. This rule applies well to Entity Bean testing. In ACL, each entity bean has its own unit testing class. For example, the TestGroup class tests the Group Entity Bean. In addition, the test cases in TestGroup only test the Group bean's functions, and none from other beans.

public static Test suite() {
   TestSuite suite = new TestSuite();
   suite.addTest(new TestGroup("testCreateGroup"));
   suite.addTest(new TestGroup("testFindGroupByGroupId"));
   suite.addTest(new TestGroup("testFindAllGroups"));
   suite.addTest(new TestGroup("testDeleteGroupByGroupId"));
   return suite;
}

List 1: JUnit Test Suite for Class TestGroup

Create the Dependent Data

In almost any real product, classes have all kinds of relationships. In our ACL example, a User always has a Group, so we must have initial Group data before we can test User. There are two ways we can go with that: create the dependent data on the fly inside the test program, or create initial data in the database. In our projects, we have a complete command line script to create database and initial test data. They are in ClearCase and shared with every programmer. Each programmer uses this script to create his own development database on his PC.

The first solution has some drawbacks:

  1. Sometimes the bean you want to test could depend on much data, e.g. the UserAccess bean will need initial data from User, Privilege, and ACL. Setting up the initial data will be quite a job. If every programmer has to do this for every bean, the accumulated effort is unacceptable from our experience.
  2. Error propagations; e.g., if you have an error when creating the Group bean, all of the subsequent tests on User and UserAccess will fail.
  3. Time-consuming; the initial data setup though EJB takes time. This contradicts one of the unit testing rules: be fast.

We find that the latter option is normally better. Here is a sample (all of the scripts are provided in the download package):

INSERT INTO GROUP (ID, NAME) VALUES ('AdminGroup', 'adminstrator');
INSERT INTO GROUP (ID, NAME) VALUES ('DevGroup', 'developer');
INSERT INTO GROUP (ID, NAME) VALUES ('OutGroup', 'outsider');

INSERT INTO USER (ID, NAME, GROUP_ID) VALUES
   ('AdminUser', 'administrator1', 'AdminGroup');
INSERT INTO USER (ID, NAME, GROUP_ID) VALUES
   ('DevUser1', 'developer1', 'DevGroup');
INSERT INTO USER (ID, NAME, GROUP_ID) VALUES
   ('DevUser2', 'developer2', 'DevGroup');
INSERT INTO USER (ID, NAME, GROUP_ID) VALUES
   ('ArchUser', 'architect', 'DevGroup');
INSERT INTO USER (ID, NAME, GROUP_ID) VALUES
   ('OutUser', 'outsider', 'OutGroup');

INSERT INTO USER_GROUP(USER_ID, GROUP_ID) VALUES('AdminUser','AdminGroup');
INSERT INTO USER_GROUP(USER_ID, GROUP_ID) VALUES('DevUser1','DevGroup');
INSERT INTO USER_GROUP(USER_ID, GROUP_ID) VALUES('DevUser2','DevGroup');
INSERT INTO USER_GROUP(USER_ID, GROUP_ID) VALUES('ArchUser','DevGroup');
INSERT INTO USER_GROUP(USER_ID, GROUP_ID) VALUES('OutUser','OutGroup');
INSERT INTO USER_GROUP(USER_ID, GROUP_ID) VALUES('ArchUser','AdminGroup');

INSERT INTO ACL(ID, DESC) VALUES('File', '');

INSERT INTO PRIVILEGE (ID, DESC) VALUES (0, 'NO_ACCESS');
INSERT INTO PRIVILEGE (ID, DESC) VALUES (1, 'READ_ONLY');
INSERT INTO PRIVILEGE (ID, DESC) VALUES (2, 'READ_WRITE');
INSERT INTO PRIVILEGE (ID, DESC) VALUES (3, 'FULL_CONTROL');

INSERT INTO USER_ACCESS(ID, PV_ID, USER_ID, ACL_ID) VALUES
   ('AdminUserAccess',2,'AdminUser','File');
INSERT INTO USER_ACCESS(ID, PV_ID, USER_ID, ACL_ID) VALUES
   ('DevUserAccess1',1,'DevUser1','File');
INSERT INTO USER_ACCESS(ID, PV_ID, USER_ID, ACL_ID) VALUES
   ('DevUserAccess2',1,'DevUser2','File');
INSERT INTO USER_ACCESS(ID, PV_ID, USER_ID, ACL_ID) VALUES
   ('OutUserAccess',0,'OutUser','File');

INSERT INTO GROUP_ACCESS(ID, PV_ID, GROUP_ID, ACL_ID) VALUES
   ('AdminGrpAccess',2,'AdminGroup','File');
INSERT INTO GROUP_ACCESS(ID, PV_ID, GROUP_ID, ACL_ID) VALUES
   ('DevGroupAccess',1,'DevGroup','File');
INSERT INTO GROUP_ACCESS(ID, PV_ID, GROUP_ID, ACL_ID) VALUES
   ('OutGroupAccess',0,'OutGroup','File');

List 2 -- create_test_data.sql

Patterns of Test Data

We will usually find test data two places: the initial test data in the database as we discussed in the previous section, and the data we used in our test program; e.g., the class TestGroup.

Here we suggest to use a different pattern of test data for initial data in the database from the one the developer used inside of the actual test program. For example, in ACL, the initial data in the database conforms to the Java class name convention style, such as "AdminGroup" and "AdminUser." We use all upper case with underscore (database convention) style, such as "GROUP_1" and "USER_1," in the test program. You can use any style you like. The point is: by having completely different data patterns, you will avoid conflicts. Without this mechanism, the test programmer can easily create the same data which is already inside the database, so that you will get the DuplicateKeyException frequently.

Pages: 1, 2, 3

Next Pagearrow