

Posted by John Thomas and Markus Clermont



This is the second of a two part blog series titled 'Taming the Beast : How to test AJAX applications'. In



Application under test



The sample application we want to test is a simple inventory management system that allows users to increase or decrease the number of parts at various store locations. The application is built using



To quickly recap from

Explore the system's functionality Identify the system's architecture Identify the interfaces between components Identify dependencies and fault conditions For each function Identify the participating components Identify potential problems Test in isolation for problems Create a 'happy path' test Let's look at each step in detail:



1. Explore the system's functionality



This is the second of a two part blog series titled 'Taming the Beast : How to test AJAX applications'. In part one we discussed some philosophies around web application testing. In this part we walk through a real example of designing a test strategy for an AJAX application by going 'beyond the GUI'.The sample application we want to test is a simple inventory management system that allows users to increase or decrease the number of parts at various store locations. The application is built using GWT (Google Web Toolkit) but the testing methodology described here could be applied to any AJAX application.To quickly recap from part one , here's our recipe for testing goodness:Let's look at each step in detail:







2. Identify the architecture

Learning about the system architecture is the next critical step. At this point think of the system as a set of components and figure out how they talk to each other. Design documents and architecture diagrams are helpful in this step. In our example we have the following components: Simple as this sounds, it is a crucial first step to testing the application. You need to know how the system functions from a user's perspective before you can begin writing tests. Open the app, browse around, click on buttons and links and just get a 'feel' of the app. Here's what our example app looks like:The app has a NavPane to filter the inventory by locations, list number of items in each location, increase/decrease the balance for items and sort the list by office and by product.Learning about the system architecture is the next critical step. At this point think of the system as a set of components and figure out how they talk to each other. Design documents and architecture diagrams are helpful in this step. In our example we have the following components: GWT client: Java code compiled into JavaScript that lives in the users browser. Communicates with the server via HTTP-RPC

Servlet: standard Apache Tomcat servlet that serves the "frontend.html" (main page) with the injected JavaScript and also serves RPCs to communicate with the client-side JavaScript.

Server-side implementation of the RPC-Stubs: The servlet dispatches the RPC over HTTP calls to this implementation. The RPCImpl communicates with the RPC-Backend via protocol-buffers over RPC

RPC backend: deals with the business logic and data storage.

Bigtable: for storing data It helps to draw a simple diagram representing the data flows between these components, if one doesn't already exist:

In our sample application, the RPC-Implementation is called "StoreService" and the other RPC-Backend is called "OfficeBackend".

3. Identify the interfaces between components It helps to draw a simple diagram representing the data flows between these components, if one doesn't already exist:

Some obvious ones are:

gwt_module target in Ant build file

"service" servlet of Apache Tomcat

definition of the RPC-Interface

Protocol buffers

Bigtable

UI (it is an interface, after all!)

4. Identify dependencies and fault conditions

With the interfaces correctly identified, we need to identify dependencies and figure out input values that are needed to simulate error conditions in the system.



In our case the UI talks to the servlet which in turn talks to StoreService (RPCImpl). We should verify what happens when the StoreService: Some obvious ones are:With the interfaces correctly identified, we need to identify dependencies and figure out input values that are needed to simulate error conditions in the system.In our case the UI talks to the servlet which in turn talks to StoreService (RPCImpl). We should verify what happens when the StoreService:

returns null

returns empty lists

returns huge lists

returns lists with malformed content (wrongly encoded, null or long strings)

times out

gets two concurrent calls

returns malformed content

times out

sends two concurrent requests

throws exceptions

Client

Gets all offices from RPC



On select, fetch items with RPC. On completion, update table.



On deselect, clear items from table.

RPCImpl

Gets all offices from RPC-Backend



Fetches all stock for an office from RPC-Backend

RPC-Backend

Scan bigtable for all offices



Query stock for a given office from bigtable.

Introduce a flag to switch

Use the proxy-pattern

Switch it at run time

Add a different constructor to the servlet

Introduce a different build-target that links to the fake implementation

Use dependency injection to swap out real for fake implementations

In addition the RPCImpl (StoreService) talks to the RPC-Backend (OfficeAdministration). Again we want to make sure the proper calls are made and what happens when the backend:To achieve these goals, we will want to replace the RPCImpl (StoreService) with a mock that we can control, and have the servlet talk to the mock. The same is true for the OfficeAdministration - we will want to replace the real RPCBackend with a more controllable fake, and have StoreService communicate with the mock instead.To get a better overview, we will first look at individual use-cases, and see how the components interact. An example would be the filter-function in the UI (only those items under a 'checked' in a checked-location in the NavPane will be displayed in the table).Our next step is to figure out the "smallest test" that can give us confidence that each of the components works as expected.Make sure that de-selecting an item removes it. For that, we need to be sure what items will be in the list. A fake RPCImpl could do just that - independent of other tests that might use the same datasource.The task is to make the Servlet talk to the "MockStoreService" as RPCImpl. We have different possibilities to achieve that:Any one of these options would do the job depending on the application. Solutions like adding a new constructor to the servlet would need production code to depend on test code, which is obviously a bad idea. Switching implementations at run time (using class loader trickery) is also an option but could expose security holes. Dependency injection offers a flexible and efficient way to do the same job without polluting production code.There are various frameworks to allow this form of dependency injection. We want to briefly introduce GuiceBerry as one of them.