- •contents
- •preface
- •acknowledgments
- •about this book
- •Special features
- •Best practices
- •Design patterns in action
- •Software directory
- •Roadmap
- •Part 1: JUnit distilled
- •Part 2: Testing strategies
- •Part 3: Testing components
- •Code
- •References
- •Author online
- •about the authors
- •about the title
- •about the cover illustration
- •JUnit jumpstart
- •1.1 Proving it works
- •1.2 Starting from scratch
- •1.3 Understanding unit testing frameworks
- •1.4 Setting up JUnit
- •1.5 Testing with JUnit
- •1.6 Summary
- •2.1 Exploring core JUnit
- •2.2 Launching tests with test runners
- •2.2.1 Selecting a test runner
- •2.2.2 Defining your own test runner
- •2.3 Composing tests with TestSuite
- •2.3.1 Running the automatic suite
- •2.3.2 Rolling your own test suite
- •2.4 Collecting parameters with TestResult
- •2.5 Observing results with TestListener
- •2.6 Working with TestCase
- •2.6.1 Managing resources with a fixture
- •2.6.2 Creating unit test methods
- •2.7 Stepping through TestCalculator
- •2.7.1 Creating a TestSuite
- •2.7.2 Creating a TestResult
- •2.7.3 Executing the test methods
- •2.7.4 Reviewing the full JUnit life cycle
- •2.8 Summary
- •3.1 Introducing the controller component
- •3.1.1 Designing the interfaces
- •3.1.2 Implementing the base classes
- •3.2 Let’s test it!
- •3.2.1 Testing the DefaultController
- •3.2.2 Adding a handler
- •3.2.3 Processing a request
- •3.2.4 Improving testProcessRequest
- •3.3 Testing exception-handling
- •3.3.1 Simulating exceptional conditions
- •3.3.2 Testing for exceptions
- •3.4 Setting up a project for testing
- •3.5 Summary
- •4.1 The need for unit tests
- •4.1.1 Allowing greater test coverage
- •4.1.2 Enabling teamwork
- •4.1.3 Preventing regression and limiting debugging
- •4.1.4 Enabling refactoring
- •4.1.5 Improving implementation design
- •4.1.6 Serving as developer documentation
- •4.1.7 Having fun
- •4.2 Different kinds of tests
- •4.2.1 The four flavors of software tests
- •4.2.2 The three flavors of unit tests
- •4.3 Determining how good tests are
- •4.3.1 Measuring test coverage
- •4.3.2 Generating test coverage reports
- •4.3.3 Testing interactions
- •4.4 Test-Driven Development
- •4.4.1 Tweaking the cycle
- •4.5 Testing in the development cycle
- •4.6 Summary
- •5.1 A day in the life
- •5.2 Running tests from Ant
- •5.2.1 Ant, indispensable Ant
- •5.2.2 Ant targets, projects, properties, and tasks
- •5.2.3 The javac task
- •5.2.4 The JUnit task
- •5.2.5 Putting Ant to the task
- •5.2.6 Pretty printing with JUnitReport
- •5.2.7 Automatically finding the tests to run
- •5.3 Running tests from Maven
- •5.3.2 Configuring Maven for a project
- •5.3.3 Executing JUnit tests with Maven
- •5.3.4 Handling dependent jars with Maven
- •5.4 Running tests from Eclipse
- •5.4.1 Creating an Eclipse project
- •5.4.2 Running JUnit tests in Eclipse
- •5.5 Summary
- •6.1 Introducing stubs
- •6.2 Practicing on an HTTP connection sample
- •6.2.1 Choosing a stubbing solution
- •6.2.2 Using Jetty as an embedded server
- •6.3 Stubbing the web server’s resources
- •6.3.1 Setting up the first stub test
- •6.3.2 Testing for failure conditions
- •6.3.3 Reviewing the first stub test
- •6.4 Stubbing the connection
- •6.4.1 Producing a custom URL protocol handler
- •6.4.2 Creating a JDK HttpURLConnection stub
- •6.4.3 Running the test
- •6.5 Summary
- •7.1 Introducing mock objects
- •7.2 Mock tasting: a simple example
- •7.3 Using mock objects as a refactoring technique
- •7.3.1 Easy refactoring
- •7.3.2 Allowing more flexible code
- •7.4 Practicing on an HTTP connection sample
- •7.4.1 Defining the mock object
- •7.4.2 Testing a sample method
- •7.4.3 Try #1: easy method refactoring technique
- •7.4.4 Try #2: refactoring by using a class factory
- •7.5 Using mocks as Trojan horses
- •7.6 Deciding when to use mock objects
- •7.7 Summary
- •8.1 The problem with unit-testing components
- •8.2 Testing components using mock objects
- •8.2.1 Testing the servlet sample using EasyMock
- •8.2.2 Pros and cons of using mock objects to test components
- •8.3 What are integration unit tests?
- •8.4 Introducing Cactus
- •8.5 Testing components using Cactus
- •8.5.1 Running Cactus tests
- •8.5.2 Executing the tests using Cactus/Jetty integration
- •8.6 How Cactus works
- •8.6.2 Stepping through a test
- •8.7 Summary
- •9.1 Presenting the Administration application
- •9.2 Writing servlet tests with Cactus
- •9.2.1 Designing the first test
- •9.2.2 Using Maven to run Cactus tests
- •9.2.3 Finishing the Cactus servlet tests
- •9.3 Testing servlets with mock objects
- •9.3.1 Writing a test using DynaMocks and DynaBeans
- •9.3.2 Finishing the DynaMock tests
- •9.4 Writing filter tests with Cactus
- •9.4.1 Testing the filter with a SELECT query
- •9.4.2 Testing the filter for other query types
- •9.4.3 Running the Cactus filter tests with Maven
- •9.5 When to use Cactus, and when to use mock objects
- •9.6 Summary
- •10.1 Revisiting the Administration application
- •10.2 What is JSP unit testing?
- •10.3 Unit-testing a JSP in isolation with Cactus
- •10.3.1 Executing a JSP with SQL results data
- •10.3.2 Writing the Cactus test
- •10.3.3 Executing Cactus JSP tests with Maven
- •10.4 Unit-testing taglibs with Cactus
- •10.4.1 Defining a custom tag
- •10.4.2 Testing the custom tag
- •10.5 Unit-testing taglibs with mock objects
- •10.5.1 Introducing MockMaker and installing its Eclipse plugin
- •10.5.2 Using MockMaker to generate mocks from classes
- •10.6 When to use mock objects and when to use Cactus
- •10.7 Summary
- •Unit-testing database applications
- •11.1 Introduction to unit-testing databases
- •11.2 Testing business logic in isolation from the database
- •11.2.1 Implementing a database access layer interface
- •11.2.2 Setting up a mock database interface layer
- •11.2.3 Mocking the database interface layer
- •11.3 Testing persistence code in isolation from the database
- •11.3.1 Testing the execute method
- •11.3.2 Using expectations to verify state
- •11.4 Writing database integration unit tests
- •11.4.1 Filling the requirements for database integration tests
- •11.4.2 Presetting database data
- •11.5 Running the Cactus test using Ant
- •11.5.1 Reviewing the project structure
- •11.5.2 Introducing the Cactus/Ant integration module
- •11.5.3 Creating the Ant build file step by step
- •11.5.4 Executing the Cactus tests
- •11.6 Tuning for build performance
- •11.6.2 Grouping tests in functional test suites
- •11.7.1 Choosing an approach
- •11.7.2 Applying continuous integration
- •11.8 Summary
- •Unit-testing EJBs
- •12.1 Defining a sample EJB application
- •12.2 Using a façade strategy
- •12.3 Unit-testing JNDI code using mock objects
- •12.4 Unit-testing session beans
- •12.4.1 Using the factory method strategy
- •12.4.2 Using the factory class strategy
- •12.4.3 Using the mock JNDI implementation strategy
- •12.5 Using mock objects to test message-driven beans
- •12.6 Using mock objects to test entity beans
- •12.7 Choosing the right mock-objects strategy
- •12.8 Using integration unit tests
- •12.9 Using JUnit and remote calls
- •12.9.1 Requirements for using JUnit directly
- •12.9.2 Packaging the Petstore application in an ear file
- •12.9.3 Performing automatic deployment and execution of tests
- •12.9.4 Writing a remote JUnit test for PetstoreEJB
- •12.9.5 Fixing JNDI names
- •12.9.6 Running the tests
- •12.10 Using Cactus
- •12.10.1 Writing an EJB unit test with Cactus
- •12.10.2 Project directory structure
- •12.10.3 Packaging the Cactus tests
- •12.10.4 Executing the Cactus tests
- •12.11 Summary
- •A.1 Getting the source code
- •A.2 Source code overview
- •A.3 External libraries
- •A.4 Jar versions
- •A.5 Directory structure conventions
- •B.1 Installing Eclipse
- •B.2 Setting up Eclipse projects from the sources
- •B.3 Running JUnit tests from Eclipse
- •B.4 Running Ant scripts from Eclipse
- •B.5 Running Cactus tests from Eclipse
- •references
- •index
50CHAPTER 3
Sampling JUnit
E Read back the handler under a new variable name.
F Check to see if you get back the same object you put in.
JUnit best practices: choose meaningful test method names
You must be able to understand what a method is testing by reading the name. A good rule is to start with the testXxx naming scheme, where Xxx is the name of the method to test. As you add other tests against the same method, move to the testXxxYyy scheme, where Yyy describes how the tests differ.
Although it’s very simple, this unit test confirms the key premise that the mechanism for storing and retrieving RequestHandler is alive and well. If addHandler or getRequest fails in the future, the test will quickly detect the problem.
As you create more tests like this, you will notice that you follow a pattern of steps:
1Set up the test by placing the environment in a known state (create objects, acquire resources). The pre-test state is referred to as the test fixture.
2Invoke the method under test.
3Confirm the result, usually by calling one or more assert methods.
3.2.3Processing a request
Let’s look at testing the core purpose of the controller, processing a request. Because you know the routine, we’ll just present the test in listing 3.7 and review it.
Listing 3.7 testProcessRequest
public class TestDefaultController extends TestCase
{ |
|
|
[... |
] |
b |
|
public void testProcessRequest() |
|
|
{ |
|
Request request = new TestRequest(); RequestHandler handler = new TestHandler(); controller.addHandler(request, handler);
C
Response response = |
controller.processRequest(request); |
D |
assertNotNull("Must |
not return a null response", response); |
E |
assertEquals(TestResponse.class, response.getClass()); F
}
}
Let’s test it! |
51 |
|
|
b First give the test a simple, uniform name.
C Set up the test objects and add the test handler.
D Here the code diverges from listing 3.6 and calls the processRequest method.
EYou verify that the returned Response object is not null. This is important because in F you call the getClass method on the Response object. It will fail with a
dreaded NullPointerException if the Response object is null. You use the assertNotNull(String, Object) signature so that if the test fails, the error displayed is meaningful and easy to understand. If you had used the assertNotNull(Object) signature, the JUnit runner would have displayed a stack trace showing an AssertionFailedError exception with no message, which would be more difficult to diagnose.
FOnce again, compare the result of the test against the expected TestResponse class.
JUnit best practices: explain the failure reason in assert calls
Whenever you use the assertTrue, assertNotNull, assertNull, and assertFalse methods, make sure you use the signature that takes a String as the first parameter. This parameter lets you provide a meaningful textual description that is displayed in the JUnit test runner if the assert fails. Not using this parameter makes it difficult to understand the reason for a failure when it happens.
Factorizing setup logic
Because both tests do the same type of setup, you can try moving that code into the JUnit setUp method. As you add more test methods, you may need to adjust what you do in the standard setUp method. For now, eliminating duplicate code as soon as possible helps you write more tests more quickly. Listing 3.8 shows the new and improved TestDefaultController class (changes are shown in bold).
Listing 3.8 TestDefaultController after some refactoring
package junitbook.sampling;
import junit.framework.TestCase;
public class TestDefaultController extends TestCase
{
private DefaultController controller;
private Request request;
private RequestHandler handler;
52CHAPTER 3
Sampling JUnit
protected void setUp() throws Exception
{
controller = new DefaultController();
request = new TestRequest();
handler = new TestHandler();
controller.addHandler(request, handler);
}
B
private class TestRequest implements Request
{
// Same as in listing 3.5
}
private class TestHandler implements RequestHandler
{
// Same as in listing 3.5
}
private class TestResponse implements Response
{
// Same as in listing 3.5
} |
|
public void testAddHandler() |
C |
{ |
|
RequestHandler handler2 = controller.getHandler(request);
assertSame(handler2, handler);
} |
|
public void testProcessRequest() |
D |
{ |
|
Response response = controller.processRequest(request); assertNotNull("Must not return a null response", response);
assertEquals(TestResponse.class, response.getClass());
}
}
bThe instantiation of the test Request and RequestHandler objects is moved to setUp. This saves you repeating the same code in testAddHandler C and testProcessRequest D.
DEFINITION refactor—To improve the design of existing code. For more about refactoring, see Martin Fowler’s already-classic book.4
Note that you do not try to share the setup code by testing more than one operation in a test method, as shown in listing 3.9 (an anti-example).
4Martin Fowler, Refactoring: Improving the Design of Existing Code (Reading, MA: Addison-Wesley, 1999).
Let’s test it! |
53 |
|
|
Listing 3.9 Do not combine test methods this way.
public class TestDefaultController extends TestCase
{
[...]
public void testAddAndProcess()
{
Request request = new TestRequest(); RequestHandler handler = new TestHandler(); controller.addHandler(request, handler);
RequestHandler handler2 = controller.getHandler(request); assertEquals(handler2,handler);
// DO NOT COMBINE TEST METHODS THIS WAY
Response response = controller.processRequest(request); assertNotNull("Must not return a null response", response); assertEquals(TestResponse.class, response.getClass());
}
}
JUnit best practices: one unit test equals one testMethod
Do not try to cram several tests into one method. The result will be more complex test methods, which will become increasingly difficult to read and understand. Worse, the more logic you write in your test methods, the more risk there is that it will not work and will need debugging. This is a slippery slope that can end with writing tests to test your tests!
Unit tests give you confidence in a program by alerting you when something that had worked now fails. If you put more than one unit test in a method, it makes it more difficult to zoom in on exactly what went wrong. When tests share the same method, a failing test may leave the fixture in an unpredictable state. Other tests embedded in the method may not run, or may not run properly. Your picture of the test results will often be incomplete or even misleading.
Because all the test methods in a TestCase share the same fixture, and JUnit can now generate an automatic test suite (see chapter 2), it’s really just as easy to place each unit test in its own method. If you need to use the same block of code in more than one test, extract it into a utility method that each test method can call. Better yet, if all methods can share the code, put it into the fixture.
For best results, your test methods should be as concise and focused as your domain methods.