Reflection + JUnit = Stable Code

When designing architecture code, unit testing can be an important process. With unit testing, units of the code are tested in a controlled environment before they are incorporated into the entire system. Unit testing allows developers to catch bugs before the code is integrated with several other classes. Also, creating reusable unit tests with frameworks such as JUnit allows the programmer to perform a regression test when other features are implemented; they simply need to run the test to verify that nothing was broken. The most important and common question surrounding unit testing is how much of the code should be tested. Ideally, you should test everything in the product. Unfortunately, this is often not possible because of deadlines and other pressures. You could easily spend more time writing tests than code. However, all architecture code should be tested because of the weight of the system that will be resting on that code. You may believe that testing is not worth the effort; after all, these objects are only beans that hold data and not business processes. However, reflection allows you to have your cake and eat it too. With reflection, you can test a data model object regardless of which properties it declares:

package oracle.hcj.reflection;
public class TestMutableObject extends TestCase {
 private Class type = null;
 protected TestMutableObject(final Class type, final String testName) {
 super(testName);
 assert (type != null);
 assert (MutableObject.class.isAssignableFrom(type));
 this.type = type;
 }
 public static Test suite(final Class type) {
 if (type == null) {
 throw new NullPointerException("type"); }
 if (!MutableObject.class.isAssignableFrom(type)) {
 throw new IllegalArgumentException("type"); }
 TestSuite suite = new TestSuite(type.getName( ));
 suite.addTest(new TestMutableObject(type, "testConstraintsExist"));
 return suite;
 }
 public void testConstraintsExist( ) {
 try {
  final PropertyDescriptor[] props =
 Introspector.getBeanInfo(type, Object.class)
 .getPropertyDescriptors( );
 for (int idx = 0; idx < props.length; idx++) {
 ObjectConstraint constraint =
 MutableObject.getConstraint(type, props[idx].getName( ));
 assertNotNull("Property " + props[idx].getName( )
 + " does not have a constraint.", constraint);
 }
 } catch (final IntrospectionException ex) {
 throw new RuntimeException( );
 }
 }
}


This class is a standard JUnit test case with a twist. It does not know what kind of class it will be testing until the suite of test cases is created with the suite( ) method. The user of the test case passes the class that he wants to test into the suite( ) method, and the test is built for that particular class:

package oracle.hcj.reflection;
public class BankDataTests {
 public static Test suite( ) {
 TestSuite suite = new TestSuite("Online-Only Bank Datamodel Tests");
 suite.addTest(TestMutableObject.suite(Account.class));
 suite.addTest(TestMutableObject.suite(AssetAccount.class));
 suite.addTest(TestMutableObject.suite(AutomaticPayment.class));
 suite.addTest(TestMutableObject.suite(BankOfficer.class));
 suite.addTest(TestMutableObject.suite(CreditCardAccount.class));
 suite.addTest(TestMutableObject.suite(Customer.class));
 suite.addTest(TestMutableObject.suite(Employee.class));
 suite.addTest(TestMutableObject.suite(ExternalTransaction.class));
 suite.addTest(TestMutableObject.suite(LiabilityAccount.class));
 suite.addTest(TestMutableObject.suite(LoanAccount.class));
 suite.addTest(TestMutableObject.suite(Loanapp.class));
 suite.addTest(TestMutableObject.suite(OnlineCheckingAccount.class));
 suite.addTest(TestMutableObject.suite(Person.class));
 suite.addTest(TestMutableObject.suite(SavingsAccount.class));
 suite.addTest(TestMutableObject.suite(Transaction.class));
 return suite;
 }
 public static void main(final String[] args) {
 TestRunner.run(BankDataTests.class);
 }
}


You can run this test using the ant script:

>ant -Dexample=oracle.hcj.reflection.BankDataTests run_example


This will bring up the JUnit test window and run each of the tests. On your healthy data model, the results will look like Screenshot-1.

Screenshot-1. Testing the constraints in a healthy data model
Java figs/HCJ_0901.gif

However, if you comment out the constraint on the balance property of Account, your test case will acquire the defect in every class that inherits from Account (see Screenshot-2).

Screenshot-2. Testing an erroneous data model
Java figs/HCJ_0902.gif

The Account class, as well as classes that inherit from Account, shows an error indicating that there isn't a constraint on the balance property. Unit testing can be expanded to test almost all facets of your data model to make sure it conforms to the rules of your design specification. Also, you can use unit testing to write tests for other objects. With this technique, you can implement tests in one location and apply them to many classes. Reflection can be used for a wide variety of other problems as well. This chapter has touched on only a fraction of the large number of things you can do with reflection. As you expand your reflection skills, you will find that you are designing more and more tools and copying less and less code. In the end, your code will be more solid and easier to maintain.

      
Comments