« 7. Basic Pattern #4 - Transporter | Main | 5. Basic Pattern #2 - Object Genie »

December 16, 2004

6. Basic Pattern - #3 Domain Test Object (DTO)

Definition

Encapsulates an application's visual components into objects that can be reused by many tests.

Discussion

We use object-oriented programming (OOP) to model real world objects inside our applications. We can also use OOP to model visual representations of the application (e.g. a shopping cart, a coupon, and a catalog) in our tests. In other words, the application uses objects to model the real world, while the tests use objects to model the application's world.

The DTO pattern groups visual representations of the application into objects that can be used by tests. Each test communicates to the DTO what data is expected but not how the data should appear (i.e. order and name of input fields to which the data applies or placement of the data on the page). The DTO then checks the test provided data against the actual data in the application. Thus, the DTO allows us to decouple the expected data from its visual representation. When the visual representation of the application changes (e.g. new table columns, new fields, and new labels), the update is done to the DTO instead of to each test. All the tests that use that DTO will be insulated from the change and will not need updating, thereby reducing maintenance costs. In essence, the DTO is a test-specific application of the Model/View/Controller (MVC) design pattern,9 where the tests are the Model and the DTO is the View.

Example

Customer tests need to check the contents of the shopping cart after certain actions are performed (e.g. adding, removing, updating quantity of cart items). A simple table can represent the shopping cart by displaying the list of items, quantity, unit price and total price.


Table 1: Visual representation of shopping cart contents.

Item Quantity Unit Price Price
Milk 1 $2.99 $2.99
Bread 2 $1.50 $3.00
       
Total     $5.99

This table is the application's representation of a shopping cart. Every time a test wants to check the cart's contents it must check this table. Thus, any changes to this table (e.g. a new column being added) could have severe test maintenance consequences. To minimize this maintenance cost, we can model the shopping cart table as a DTO and have tests interact with the DTO instead of with the table directly. The following code sample demonstrates checking the shopping cart page without using a DTO. This is then contrasted with doing the same check using a DTO.


Figure 6: Checking shopping cart contents without DTO.

public void checkCart_noDto() {
String [][] expectedCartTableCells = {
{"Item", "Quantity", "Unit Price", "Price"},
{"Milk", "1", "$2.99", "$2.99"},
{"Break", "2", "$1.50", "$3.00"},
{"", "", "", "", },
{"Total", "", "", "$5.99"}
};
//tool-specific way to retrieve table cell contents
String [][] actualCartTableCells = getCartTableCells();

//check every web table cell of the cart page
assertEquals(actualCartTableCells.length, expectedCartTableCells.length);
for(int i=0; i assertEquals(expectedCartTableCells[i].length, actualCartTableCells[i].length);
for(int j=0; j assertEquals(expectedCartTableCells[i][j], actualCartTableCells[i][j]);
}
}
}

Without using the DTO pattern, we are forced to specify the expected content in a rigid order that is vulnerable to application changes. Every time we need to check the contents of the shopping cart page, we would have to repeat the same checking code, but with different values. For example, we would need to replicate the same checking code after adding to the cart, removing from a cart, updating the quantity of items in the cart, etc. The maintenance problem occurs when the layout of the cart changes and we would have to update all the checking code for each cart action. For instance, the application might change so that the shopping cart page has a new column called "Discount" which contains percentage values of the discount for an item. The application might also change by having the quantity and unit price columns switched. In both of these cases, all our test code would break because now the web table wouldn't match our expected table values. By using a DTO, we can avoid this problem and be more adaptable to application changes. The following code demonstrates how a DTO object can be used:


Figure 7: Checking shopping cart contents with DTO.

public void checkCart_withDto() {
ShoppingCart shoppingCart = new ShoppingCart();

//...add/remove/edit cart using other patterns

ShoppingCartDto shoppingCartDto = new ShoppingCartDto();

//setup expected values
shoppingCartDto.setItem("1", "2.99", "2.99", "Milk");
shoppingCartDto.setItem("2", "1.50", "3.00", "Bread");
shoppingCartDto.setTotal("5.99");
shoppingCartDto.check(shoppingCart);
}

public class ShoppingCartDto {
public void setItem(String pQuantity, String pUnitPrice, String pTotalPrice, String pName) {
//code to store expected item internally would follow
}

public void setTotal(String pTotal) {
//code to store expected total internally would follow
}

public void check(ShoppingCart pShoppingCart) {
//code to check expected versus actual shopping cart contents would follow
}
}

From Figure 7, you can see that the DTO is a separate class that is instantiated and configured by test cases. Note that the DTO knows how to check itself so tests never have to explicitly specify the column order or any other details about the shopping cart table. The tests simply tell the DTO the items that are expected to be in the cart and then ask the DTO to check the cart contents. Now even if the entire shopping cart page changed so that the cart is no longer represented by a table, the update would be done to the DTO while leaving the tests insulated from such a drastic change.

Posted by Misha Rybalov at December 16, 2004 05:22 PM

Comments

Post a comment




Remember Me?