18.1. What's Covered Here?
JUnit is a generic Java unit testing framework that can be used in any Java environment. You can download this tool from http://www.junit.org. We've written and executed the unit tests demonstrated in this chapter using a JDK 1.4.2 environment.
Cactus is a JUnit extension that allows you to write tests that execute inside a J2EE container. The Cactus framework supports unit testing of various J2EE
18.1.1. Other Tools to Consider
We chose to cover JUnit and Cactus in this tutorial because, as with the other tools we've
18.2. Unit Testing Concepts
Before we dive into the details of testing Java code using JUnit and testing J2EE
18.2.1. Units and Dependencies
The most obvious question to ask about unit testing is, "What's a unit?" The most conservative definition of a testable "unit" is an entirely independent module of code that can be
Of course, achieving this goal of a totally independent unit of code is not entirely realistic. Virtually any code module depends on other code modules to function properly. Functional decomposition encourages algorithms to be broken down into subfunctions that are invoked by higher-order functions to solve a problem. Object-oriented design has the analogous principle of delegation of responsibilities to subordinate classes used by higher-order classes to implement particular behaviors. These common divide-and-conquer approaches to software architecture naturally lead to interdependencies among code elements.
Given this fact, achieving the goal of unit testing (i.e., verifying the valid operation of each unit in the system) takes some forethought and planning in terms of defining your test suite. The dependencies between units need to be
18.2.2. Unit Testing Versus Integration Testing
It's important to note at this point the distinction between unit testing and integration testing. While unit testing strives to verify the correct operation of individual units of code in isolation, integration testing attempts to verify the correct operation of multiple units working together in a cooperative fashion, integrated together as they would be in the real runtime environment. In the remainder of this tutorial chapter on unit testing, we will occasionally stray into the realm of integration testing with some of our examples. We'd ask the testing experts reading this to forgive us for this breach of orthodoxy, and we'd ask those who are new to unit testing to remember that the distinction between unit and integration testing is important and to keep it in mind when designing tests.
18.2.3. White Box Versus Black Box Testing
It's important to recognize what kind of testing you're attempting when defining a specific test or even a suite of tests. Black box testing, as mentioned earlier, refers to the testing of the functional specifications of a unit of code from the standpoint of an external interface specification. White box testing refers to validating the functionality of a unit from the standpoint of its internal implementation.
If we were writing black box tests for our shopping cart, we would simply encode the behavioral specifications into tests that exercise that behavior and check for expected results. Our tests would do things like add an item and verify that it is recorded in the shopping cart, add various combinations of types of items, and check that the number and price of the items in the cart are
If we were engaging in white box testing , we would focus on the internal implementation details of the variable-
In practical testing scenarios, you will find yourself creating a mix of black box and white box tests, sometimes referred to as
testing. An initial suite of tests may be
18.2.4. Keep the Tests Simple
If you haven't written tests beforeand if you haven't divined it from the discussion so faryou'll soon discover the main dilemma with testing: it involves more work. On top of sorting out the requirements, designing the architecture, and writing the code, there's the additional effort of writing tests for the code.  The benefits of testing, as discussed earlier in this chapter, are conceptually obvious but sometimes difficult to remember when you're faced with a deadline.
We mention this here to
Simple tests also help keep your test results clean and unambiguous. You want to avoid complicating the picture when it comes to evaluating the test results. If there's a lot of logic tied up in the code that drives the tests, you run the risk of bugs in the test code masking
18.2.5. Test Coverage
There's an adage in software engineering that states, "If you didn't test it, it doesn't work." In essence, this implies that you should assume that any code that hasn't been tested will fail at some point. This principle leads you naturally to the issue of determining what code has been tested and what code hasn't. In a real-world application, even one of moderate
Having said all this, it's also important to be realistic about test coverage. It's unreasonable to expect to have 100% coverage of your code in your test suite. A software unit, whether it's a function, an object, a component, or a web service is made up of a set of code consisting of many possible logical
Also in the category of test coverage is the management of evolving code. Once you accept the goal of a test suite that covers the critical functionality of your code, you then have to accept the reality that the test suite must