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:
- Sometimes the bean you want to test could depend on much data, e.g. the
UserAccessbean will need initial data fromUser,Privilege, andACL. 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. - Error propagations; e.g., if you have an error when creating the
Groupbean, all of the subsequent tests onUserandUserAccesswill fail. - 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.