July 25, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Guicing Up Your Testing

  • June 21, 2007
  • By Dick Wall
  • Send Email »
  • More Articles »

In what I hope will be the first of several articles about Guice, a new lightweight dependency injection container from Bob Lee and Kevin Bourrillion from Google, this article examines the simplest and most obvious use case for the Guice container, for mocking or faking objects in unit tests. In future articles I will examine other, more ambitious areas where it can be used, including dependency elimination in large code bases.

If you are familiar with the idea of mock or fake objects, this article may seem like slow going (apart from the Guice parts, hopefully). However, if you have not used mock or fake objects before, this article will not only cover their use, but demonstrate how these techniques go hand-in-hand with dependency injection.

The Test Case

To illustrate the use of Guice, you need an example unit testing setup. I settled on something pretty simple—an invoice class that uses a sales tax rate manager (another object) to look up a sales tax percentage for a customer. The invoice then adds it on to the total sum of the line item costs in the invoice.

The twist is, you are going to pretend that the tax rate manager looks up the tax rate for a customer based on the customer ID (say it varies depending on their state, which is information that the tax rate manager gets from the customer ID). You'll also throw in there that the poor old database backing this tax rate lookup is overloaded, and takes around five seconds for any query or update operation (this really isn't much of a stretch for a large and heavily used database—believe me).

Your test objects might look something like this:

public class Invoice {

   /** A Simple public inner and immutable class to hold line items
    * for the invoice, including the title, description and cost
    */

   public class Item {
      private final String title;
      private final String description;
      private final BigDecimal cost;

      .... (constructors and getters here)
   }

   private final Customer customer;
   private List<Item> lineItems;
   private final DbTaxRateManager taxRateManager;

   public Invoice(Customer customer) {
      this.customer = customer;
      // set up the line item list
      this.lineItems = new ArrayList<Item>();
      this.taxRateManager = DbTaxRateManager.getTaxRateManager();
   }

   public void addItem(String title, String description, double amount) {
      lineItems.add(new Item(title, description, amount));
   }

   private BigDecimal getTotal() {
      BigDecimal runningTotal = new BigDecimal(0);
      for (Item item:lineItems) {
         runningTotal = runningTotal.add(item.cost);
      }
      return runningTotal;
   }


   public BigDecimal getTotalWithTax() {
      BigDecimal total = this.getTotal();

      // now find the tax rate for the customer
      BigDecimal taxRate =
      this.taxRateManager.getTaxRateForCustomer(
         customer.getCustomerId());

      // and calculate the taxed total
      BigDecimal multiplier = taxRate.divide(new BigDecimal(100));

      // Boy, Big Decimal might be accurate, but it certainly is
      // ugly to do math with...
      return total.multiply(multiplier.add(new BigDecimal(1)));

   }
}

The rest of this class, along with the supporting object classes, can be seen by downloading the project zip file.

Take a look at this implementation. From the constructor, observe the line:

this.taxRateManager = DbTaxRateManager.getTaxRateManager();

This is getting the tax rate manager instance from a pretty standard mechanism in many modern implementations: a singleton. I personally believe the singleton pattern, at least in the way most people implement it (using a static getter method and a single private internal instance) to be one of the most harmful patterns from the Gang of Four book (and the numerous other patterns authorities out there). A singleton implemented this way is particularly harmful because it is very easy to grasp, and very seductive, but results in unintended consequences in many different areas.

In this particular case, the singleton implementation of DbTaxRateManager means that any time you create an invoice, you are bound to that implementation of the tax rate manager. This is the point of a singleton—it makes it dead easy to get the tax rate manager and use it, but beyond that it pretty much sucks.

For example, when you want to test this simple invoice class, that test is going to use the DbTaxRateManager implementation. This means it is going to hit the database, and with your stated database performance, this means about 5 seconds per update or query.

Further down, in the GetTotalWithTax method, you see the following usage:

BigDecimal taxRate =
   this.taxRateManager.taxRateForCustomer(customer.getCustomerId());

That's 5 seconds gone right there in that one lookup. Now, if you look in the test class:

private static final int CUST1_ID = 12341234;
private static final int CUST2_ID = 43214321;

@Before
public void setUp() throws Exception {
   // set up a couple of customer IDs with tax rates we can use
   DbTaxRateManager taxRateManager=DbTaxRateManager.getTaxRateManager();
   taxRateManager.setTaxRateForCustomer(CUST1_ID, 5.5);
   taxRateManager.setTaxRateForCustomer(CUST2_ID, 6.25);
}

Here, you have set up a couple of fake customer IDs and put them into the database with two different tax rates. Two updates, another 10 seconds.

So, by running this test, best case (doing only one tax lookup and the two rate assignments), the test is going to take over 15 seconds to run. Imagine a thousand unit tests that take 15 seconds each; you are going to be waiting a while.

That singleton is killing you performance-wise. There are all sorts of schemes to improve this situation. Some people recommend running some kind of alternative, lightweight alternative database and changing the database connection details (for example, for some functional testing, Derby or JavaDB are becoming a popular option).





Page 1 of 5



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel