A Concrete Example
So where do we start? Let's look at a typical component of this system, identify what we want to test, and then choose a strategy of how to test.
A fairly typical component in this system receives a JMS message that contains a payload of an XML document. The XML document is fairly large (400K or so) and describes a financial transaction. The component's job is to read in the message, parse the XML, populate a couple of database tables based on the message contents, and then call a stored procedure that processes the tables.
The sequence diagram in Figure 3-1 helps illustrate the message flow for this component.
Figure 3-1 Sequence diagram for a typical component
Listing 3-1 shows the rough outline of the code we'd like to test.
Listing 3-1. Existing message processor for the legacy component
public class ComponentA implements javax.jms.MessageListener { private Log log = LogFactory.getLog(ComponentA.class); public void onMessage(Message message) { java.sql.Connection c = null; PreparedStatement ps = null; TextMessage tm = (TextMessage)message; String xml = null; try { xml = tm.getText(); // XMLHelper is a util class that takes in an XML string // and parses it and returns a document Document doc = XMLHelper.parseDocument(xml); // manipulate the document to get at various elements // DatabaseHelper is a util class to look up and return a // database connection c = DatabaseHelper.getConnection(); String sql = ""; // create SQL to call in db ps = c.prepareStatement(sql); // populate sql ps.executeUpdate(); String spSql = "<sql to execute stored proc>"; // call the stored procedure c.prepareCall(spSql).execute(); } catch(Exception ex) { log.error("Error processing message " + message + " with data " + xml,ex); } finally { if(c != null) { try { if(ps != null) { try { ps.close(); } catch(SQLException e) { log.error("Error closing statement", e); } } c.close(); } catch(SQLException e) { log.error("Error closing connection", e); } } } } }
The focus of this exercise is to ensure that we test our component. A vital aspect of the process is also explicitly defining our test goals and nongoals up front, including the assumptions we're making.
Goals
Any functionality that we'd like to explicitly verify or check is considered one of the prime goals of the test process. For our specific case, we'd like to meet the following three goals.
- We will create a success test. We want to ensure that if we receive a valid XML message, we process it correctly and update the correct database tables, and the stored procedure is also successfully called.
- We will model different scenarios. We would like to be able to feed a variety of XML documents to our test to be able to easily add a growing body of sample data and use it for regression testing.
- We will institute explicit failure tests. Failure behavior should be captured and tested so that the state of the component when it fails internally is predictable and easily captured.
Nongoals
Equally important as choosing goals is identifying nongoals. These are tasks that, if we're not careful, we might accidentally end up testing, thus having our tests focus on the wrong thing. We have three nongoals in our case.
- We will not test the JMS provider functionality. We assume that it is a compliant implementation that has been correctly configured and will successfully deliver the intended message to us. The JMS API allows us to work with the TextMessage object. We can assume that this object allows us to get at the message contents without throwing any exceptions and that it will be correctly delivered. Finally, we can always have separate integration tests that verify the system's behavior end-to-end.
- We will not perform catch-all error testing. Failure test should model explicit and reproducible failure modes. A failure test that, for example, checks what happens if a NullPointerException is thrown is somewhat useless.
- We will not test APIs. The behavior of the JDBC driver is not the test subject, for example. It is also important to ensure that all our tests focus on our business functionality and to avoid tests that test Java language semantics. Therefore, we are not interested in verifying that the XML parser is able to parse XML; we assume it can.