9.1 Abstract Persistence Interface


9.1 Abstract Persistence Interface

Consider a small application to maintain customer relationships, a tiny customer relationship management (CRM) system. The persistent object model of this example system has stabilized in the course of the first few iterations to the structure shown in Figure 9.1. In the center is the customer (Customer), who is assigned to exactly one category (CustomerCategory), and who can be contacted an arbitrary number of times (CustomerContact).


Figure 9.1: Object model of the CRM application.

A naive testing approach handles persistent objects like all other objects and uses a database like an internal resource. Opting for this approach often means that we will have a persistence interface that is either static or implemented as a singleton. The following static database class is conceivable for the objects represented in Figure 9.1:

 public class CRMDatabase {    public static void initialize(String dbURL)       throws CRMException {...}    public static void shutdown() throws CRMException {...}    public static CustomerCategory createCategory(String name)       throws CRMException {...}    public static void deleteCategory(CustomerCategory category)       throws CRMException {...}    public static Set allCategories() throws CRMException {...}    public static Customer createCustomer(String name,       CustomerCategory category) throws CRMException {...}    public static void writeCustomer(Customer customer)       throws CRMException {...}    public static void deleteCustomer(Customer customer)       throws CRMException {...}    public static Customer getCustomer(String id)       throws CRMException {...}    public Set allCustomers(CustomerCategory category)       throws CRMException {...} } 

There are methods for initializing and stopping the database and for creating, writing, deleting, and retrieving categories and customers. CustomerContact instances depend on customer objects and are stored, written, and deleted through these objects.

One of the important tasks of this CRM system is to create regular reports. For example, a daily report (DailyReport) determines the number of customer contacts with specific customer categories. The following piece of code was taken from the pertaining test class:

 public class DailyReportTest extends TestCase {    private DailyReport report;    private Calendar reportDate;    private CustomerCategory catFortune100,catSmallCompany;    private final String DB_URL = "jdbc:odbc:CRM";    private List customersToDelete = new ArrayList();    protected void setUp() throws Exception {       reportDate = Calendar.getInstance();       report = new DailyReport(reportDate);       CRMDatabase.initialize(DB_URL);       catFortune100 =          CRMDatabase.createCategory("Fortune 100");       catSmallCompany =          CRMDatabase.createCategory("small company");    }    protected void tearDown() throws Exception {       Iterator i = customersToDelete.iterator();       while (i.hasNext()) {          Customer each = (Customer) i.next();          CRMDatabase.deleteCustomer(each);       }       CRMDatabase.deleteCategory(catFortune100);       CRMDatabase.deleteCategory(catSmallCompany);       CRMDatabase.shutdown();    }    public void testAllContacts() throws Exception {       Customer customer1 = CRMDatabase.createCustomer(          "Customer 1", catFortune100);       customersToDelete.add(customer1);       Customer customer2 = ...       Customer customer3 = ...       Calendar dayBefore = (Calendar) reportDate.clone();       dayBefore.add(Calendar.DATE, -1);       customer1.addContact(dayBefore, "note 1");       customer1.addContact(reportDate, "note 2");       CRMDatabase.writeCustomer(customer1);       customer2.addContact(reportDate, "note 3");       CRMDatabase.writeCustomer(customer2);       customer3.addContact(dayBefore, "note 4");       CRMDatabase.writeCustomer(customer2);       List contacts =          report.allContactsForCategory(catFortune100);       assertEquals(1, contacts.size());       // ...    } } 

The following problems emerge:

  • Although the DailyReport class should be fully independent from our database, the unit tests for this class require extensive code to initialize the database and create objects in the database itself. This code is errorprone and causes long runtime.

  • The complete deletion of all previously created persistent objects from tearDown() requires active cooperation in the test code by using customersToDelete.add(...) within the test method. If we forget this just once, our database will eventually be in an undesirable state.

  • In addition, we make ourselves conditional upon the availability of the database. A failure in the connection or excessive database-specific resources (e.g., sessions) would be enough to cause all subsequent tests to "say goodbye" with strange exceptions and error messages.

Again, the Dependency Inversion Principle (see Chapter 6, Section 6.3) offers a way out of this tricky test situation. In the above code example, the DailyReport class depends directly upon the database interface. This violates the rule stating that high-level modules should not depend on lower-level modules. We break this dependence by encapsulating persistence in an abstract interface:

 public interface CRMPersistence {    void shutdown() throws CRMException;    CustomerCategory createCategory(String name)       throws CRMException;    void deleteCategory(CustomerCategory category)       throws CRMException;    Set allCategories() throws CRMException;    Customer createCustomer(String name,       CustomerCategory category) throws CRMException;    void writeCustomer(Customer customer) throws CRMException;    void deleteCustomer(Customer customer) throws CRMException;    Customer getCustomer(String id) throws CRMException;    Set allCustomers(CustomerCategory category)       throws CRMException; } 

All currently static methods of the CRMDatabase class are now found in CRMPersistence, with one exception, initialize(String url) is an implementation detail so that it has no business in the abstract interface.




Unit Testing in Java. How Tests Drive the Code
Unit Testing in Java: How Tests Drive the Code (The Morgan Kaufmann Series in Software Engineering and Programming)
ISBN: 1558608680
EAN: 2147483647
Year: 2003
Pages: 144
Authors: Johannes Link

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net