Test Module
by kirk knoernschild
Statement
Each module should have a corresponding test module.
Description
Writing tests is one of the most important activities you should perform as a developer. Create a robust suite of tests has significant advantages, both long and short term. Short term, tests help you verify that the code you write does as you intend. Creating tests also helps you design your system. Since classes should be independently testable, a natural by-product of test driven development is that your classes will exhibit lower degrees of coupling.
Test driven development has long-term benefits too. When refactoring your code, or adding new functionality to an existing code base, a suite of tests can be repeatedly run to guarantee that your changes haven’t broken anything that once worked. Inevitably though, bugs will surface and tests can help tremendously in proving the source of the bug and then help you fix it. When bugs do surface, most developers form a mental picture of the cause of the bug, and this helps them determine the place in the system to begin looking. But it always seems that some developers have better luck fixing bugs than others. It’s a frustrating experience when you’re told a bug has been fixed, only to find that either it hasn’t been fixed correctly, or that fixing the bug has caused another area of the system to break. Why do some developers have better luck fixing bugs that others? A few simple scenarios should yield some answers.
Lazy developers will operate on their initial hunch and simply change the area of the system they suspect is causing the error, and release those changes without proper test to other developers. Lazy developers typically experience very little consistent success with fixing bugs.
A better approach taken by some developers is to duplicate the error by running the application using the same conditions as when the bug was encountered. Once the error is duplicated, they’ll typically use a debugger or logging statements to nail down the exact location of the bug. After the location is nailed down and the code has been corrected, they’ll run the application again to prove the bug has been fixed. While some testing is better than no testing, this approach is also seriously flawed. Running the application doesn’t allow you to focus specifically on the source of the bug because you are testing the application as a whole. Additionally, without a suite of test cases, you may be able to prove that your change fixed the problem in the scenario it was discovered, but you cannot guarantee that your changes didn’t break something elsewhere in the application. It’s unrealistic to expect each developer to test the entire application each time a change is made. Few developers are disciplined enough to repeatedly perform such a mundane exercise. Another disadvantage is that this approach is not automated and repeatable, and you’ll always run the risk that future changes will break it again. You may fix the problem initially, only to find that future changes introduce a similar problem. Proving that you fixed the problem today doesn’t prove that the problem won’t surface again tomorrow. Developers adopting this approach typically introduce more bugs than they fix.
Those developers who use test driven development typically have a greater degree of success in fixing bugs since a robust suite of test cases already exist that prove other areas of the system still function as intended. However, you still have to be very judicious in your approach to correcting the error. Simply fixing the source of the bug and running the existing suite of tests to prove that the bug has been corrected won’t always work.
The most successful developers typically have a very regimented approach. The approach goes something like this. Their first step is to create a new test case that reproduces the problem. The reasoning here is that if the existing suite of tests don’t reproduce the problem, then the existing suite of tests don’t test that condition, and a new test is needed. Once the problem has been reproduced, developers will fix the bug and run the test again. This time, the test should pass. If the test does pass, you can start feeling pretty good that you’ve fixed the error. But you can’t stop here. You should also run all other tests within the application to prove that your changes haven’t broken something else. Only after you do this can you feel reasonably comfortable knowing the problem has been fixed.
Adopting a sound approach to testing involves much more than simply saying that you write tests. A robust testing strategy means that you’re working hard to write classes that can be tested independently. A unit test emphasizes testing classes as a standalone unit. This typically implies that you want to decouple the class you’re testing from any outside forces, such as a database or legacy integration with a backend system. Testing classes independently implies that you are devoting effort to decoupling your classes by injecting dependencies into the class under test. This means that the classes you’re testing must depend on abstract classes or interfaces for the services they use, and the test case will inject a stub into the class under test. The stub is a simple implementation of the abstraction that exists for testing purposes only, and isn’t used in versions of the system running outside of the test suite.
Test ing classes independently using unit tests is valuable, but it’s also valuable to test the integration aspect of multiple classes or components. For instance, you might create an integration test that retrieves information from a database table to prove connectivity with the backend database, or to prove that when two components are wired together, they continue to function as expected. Any test that requires access to a force external to the class originally under test is considered an integration test. While integration tests are useful, you should be cautious to avoid testing business logic in your integration tests. You should not check that the result of the test is a specific data value, but instead focus on the structure of the information being what you expect, that the connectivity between two entities is functional, or to ensure the integration point is still what you expect. Becoming “test infected” means that you aren’t writing tests because you’ve been told you need to, but that you create a suite of unit and integrations tests to help you write higher quality code and design more resilient software.
There are other types of tests that can be very useful as well. A design test allows you to make assertions about the design metrics of the system. Tools such as JDepend can be used to create tests that check for dependencies between packages. Performance tests allow you to verify the response time of a test. JunitPerf offers extensions to the Junit testing framework that allows you to write performance tests. Finally, functional tests allow you to create user level tests. Tools such as JwebUnit or Fitnesse allow you to create functional tests.
Our discussion to this point has served as primarily a review of sound testing strategies for class level testing. Assuming you are devoted to creating robust test suites, there is no magic in creating a TestComponent. If you’re applying some of the other useful patterns in this book, a TestComponent is a very natural by-product, and is mainly an exercise in how you bundle your test cases. For any set of classes bundled in a component, the corresponding test cases are bundled in a separate TestComponent. But similar to a unit test case that should test a class independently, a TestComponent should also be able to test a component while only depending on components required by the component under test. You should avoid introducing any new dependencies from within the test component. Otherwise, you cannot say for certain that you’re testing only the component’s functionality. A good rule of thumb when creating a TestComponent is that the TestComponent should be one level higher than the component under test. This helps ensure you aren’t introducing additional dependencies that aren’t required.
Recall that levelizing your component hierarchy requires that you have AcyclicRelationships between all components. TestComponents should also have AcyclicRelationships. If you find that the TestComponent is more than a single level higher, it’s a good indication that the TestComponent is using another component’s classes as stubs for the component under test. You have to give careful consideration to the placement of integration, design, performance, and functional tests. It’s very likely that these types of tests will introduce undesirable TestComponent dependencies. Strategies for dealing with the placement of these other types of tests will be discussed next. Figure 1 illustrates a Test Module.

Figure 1: Test Module
Implementation Variations
You’ll want to share some utility classes across TestComponents. In this situation, you can either duplicate the utility classes, or create a lower level test utility component that contains the test utility classes.
It is certainly tempting for a TestComponent to define dependencies on other components that contain implementations for classes within the component under test. While resisting this temptation is difficult, it’s also important. Defining additional dependencies on other components within your test component will save you a bit of time in creating the test cases, but it will also compromise the integrity of your test. By using classes in other components as your stubs, you cannot be certain that a failing test case is the result of a flaw in the component under test, or a bug in the component classes you are using as your stubs.
Notes on Integration vs. Unit tests here
If you’re creating integration, performance, functional or design tests, you might consider splitting these integration tests out into another separate TestComponent if the integration tests introduce dependencies on other components. Using a LevelizedBuild can help in determining where these other tests should be placed.
There are some cases, however, where a TestComponent will need, and even want, to introduce additional dependencies. For instance, if the component under test relies heavily upon database interaction, it’s likely that the connections to the database are obtained from your J2EE containers javax.sql.DataSource. Ideally though, you’ll execute your test outside the container’s environment. In such situations, you’ll find you want to introduce new dependencies to components that allow you to execute the tests standalone. To deal with database connections, a good approach would be to use the Commons Database Connection Pool (DBCP) component. Of course, another approach well worth considering is creating your classes so that they can be tested independent of a database connection.
If you leverage Abstract Modules, you’ll be able to create mocks or stubs to ensure modules can be test independently of each other. Figure 2 illustrates this.

Figure 2: Test Module with Abstract Module
Consequences
Test modules increase testability by allowing you to test modules independent of each other. Once a modules functions correctly as an independent unit, any error is the result of integration with other modules. Application level integration tests can be helpful in this situation.
Sample Code
Recall the example using a Callback in the discussion on the Acyclic Relationships pattern. Figure 3 illustrates the relationship between the customer.jar and billing.jar modules resulting from the relationship between the Customer and Bill classes.

Figure 3: Module Relationships
Based on these module relationships, we can already create a separate test module for the billing.jar module. Listing 1 illustrates the test case to test the payment functionality of Bill.
package com.kirkk.test;
import junit.framework.TestCase;
import java.math.BigDecimal;
import java.util.*;
import com.kirkk.cust.*;
import com.kirkk.bill.*;
public class PaymentTest extends TestCase {
public PaymentTest(String arg0) {
super(arg0);
}
public static void main(String[] args) {}
public void testPayment() {
Customer customer = new Customer();
customer.createBill(new BigDecimal(500));
Iterator bills = customer.getBills().iterator();
while (bills.hasNext()) {
Bill bill = (Bill) bills.next();
BigDecimal paidAmount = bill.pay();
assertEquals("Paid amount not correct.", new BigDecimal(485).setScale(2), paidAmount);
}
}
public void testPaymentWithoutCustomer() {
Bill bill = new Bill(new DiscountCalculator() {
public BigDecimal getDiscountAmount() { return new BigDecimal(0.1); }
}, new BigDecimal(500));
BigDecimal paidAmount = bill.pay();
assertEquals("Paid amount not correct.", new BigDecimal(450).setScale(2), paidAmount);
}
}
This test case results in the relationships between modules shown in Figure 4, where we can now see the billingtest.jar test module.

Figure 4: The PaymentTest Module
The PaymentTest is actually an integration test because it uses the Customer to to test the Bill.
Wrapping Up
Like unit testing, where classes are ideally tested in isolation, a test module allows you to test modules in isolation. A test module can help identify undesirable module dependencies. Then, utilizing other patterns, these dependencies can be broken if that’s the desirable course of action. Unlike unit testing, a test module is a more granular kind of test. Test modules also provide excellent insight on how to configure a module and interact with its Published Interface.