Zend certified PHP/Magento developer

Testing OO Systems, Part 2: Testing

In my last column, Testing OO Systems, Part 1, I talked a lot about the notion of data abstraction in the
context of OO systems. In particular, objects should be opaque to the
outside world. You talk to them by sending messages, which request that
the object does something for you.
The main idea is that you should be able to
replace a class’s implementation with another, completely different,
implementation without impacting any of the “client” classes — the classes
whose objects use your class. You do that by implementing interfaces that
do work rather than get information. The principle is summarized in many
ways: “Ask for help, not for information,” “Ask the object that has the
information to do the work for you” (delegation), “Talk only to your
friends” (the Law of Demeter), and so forth. If you follow these principles,
then your classes will tend not to have getter/setter functions (“accessors”
or “mutators”), simply because they aren’t needed any more.

The challenge that I covered in Part 1 is how to test classes
that hide their internal implementation and state.
The basic solution is to ask the
object to do something and then watch what it does. Stimulate the
object under test (OUT),
then look at the clients (the objects that your OUT
talks to). The basic approach is to “mock” (write lightweight simulations
of) the client classes, either directly or by using a mocking framework
like Mockito.
Your mocks either monitor the OUT’s
behavior (so you can ask them how the OUT behaved after the test runs)
or just report incorrect behavior as soon as they detect it.

Last month’s examples were pretty simple, though.
You could test everything you needed to test by looking at purely external
behavior. You could look at the OUT as an impenetrable sphere.

The Donut

More often than not, however, the OUT is more of a donut than a sphere.
Outside the ring
are the client classes that comprise the users of your OUT. Inside
the ring are the objects that your OUT uses internally. For example, it’s
easy enough to test a Servlet by giving it an artificial URL request and
then looking at the string that it outputs. (We’ll see how to do that
momentarily). However, in order to pull this test off, you’ll probably have
to control things like the database communication. For example, you might
want the Servlet you’re testing to use a test version of your database, or
even better,
you might want to pull the database out of the test entirely and just
simulate the existence of the real database with a few lightweight classes.

That last approach is really the best.
You don’t have to actually get a database up and running
in order to test your class, and you even can write and test the class before the
database is implemented. If you don’t need a working database,
your development process won’t be “blocked”
if the database happens to be busy with other projects. You also won’t need to
get a real database up and running on your workstation.
(Of course, you may want to run tests both with and without the real
database, which isn’t a problem at all.)

The tricky part of the inside-the-donut parts of the test
is that you don’t want your
object to know that it’s being tested. It’s just not a valid test if your
class has if statements that select between real and test databases or
objects because you’re not testing the real paths through the code in that
case. You should never have to modify an object to test that object.

Testing a Servlet

The easiest way to understand the process is to look at some sample code (which I’ve stripped
down considerably so that the relevant parts won’t be lost in the clutter). Let’s start by looking at a simple
Servlet (Listing One).
This servlet handles a simple HTTP post. It reads the body of the post from the request object, then
generates a response.

Listing One

package com.holub.experiments;

import javax.servlet.*;
import javax.servlet.http.*;

@WebServlet("/sample")
public class SampleServlet extends HttpServlet
{
	private static final long		serialVersionUID = 1L;
	private static final Logger 	log = Logger.getLogger(TransactionHandler.class);
	
    @Override public void init()
	{	
    	//...
    }
    
    @Override public void destroy()
    {  
    	//...
    }
    
	protected void doPost(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
	{
		InputStream inputStream = request.getInputStream();

		//...
		if( error )
			response.sendError( HttpServletResponse.SC_BAD_REQUEST, "Explain what's wrong with the request" );

		OutputStream outputStream = response.getOutputStream();
		PrintWriter  outputWriter = new PrintWriter( outputStream );
			
		response.setCharacterEncoding("UTF-8");
		response.setContentType		 ("text/xml");
		
		outputWriter.write("The Output");
		
		outputWriter.close();
		inputStream.close();
	}
    
	/** For testing only. Provides public access to doPost */
	public void testDoPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
	{	 doPost(request, response);
	}
}

We can test this servlet by mocking the input stream created by the request to feed the servlet
a test string, then run the servlet manually, and finally look at the string that it writes to the
output stream to make sure that it’s what we expect.

The only oddity is that the actualy doPost(...) method has to be protected, thus I provided
a simple public pass-through wrapper at the bottom of the file so that I can call doPost(...) from
my test.

The mock input and output streams are defined in Listing Two
and Listing Three, respectively..

The input stream’s constructor is passed a String, the characters of which are returned, one at a time, from read.
The one subtly here is that I’ve inserted a short, but random, delay into the first read request to simulate network
latency.

Listing Two

package com.ecmhp.mockobjects;

import java.io.*;
import javax.servlet.ServletInputStream;
import org.apache.log4j.Logger;
import static org.junit.Assert.*;

/** Simulate a ServletInputStream for testing. A random delay
 *  is put in front of the first call to read() to simulate
 *  network latency.
 * 
 * @author allenh
 */

public class MockServletInputStream extends ServletInputStream
{
	private static Logger log = Logger.getLogger( MockServletInputStream.class );
	
	private MockServletOutputStream out = null;
	private Reader 					inputReader = null;
	private StringBuilder			charactersRead = new StringBuilder();
	
	private boolean needToInsertDelay = true;
	private long	maxDelay = 250;	// defaults to .5 seconds
	
	@Override public int read() throws IOException
	{	
		if( needToInsertDelay )
		{	// Simulate HTTP delays by sleeping (on the first call only).
			
			needToInsertDelay = false;
			if( maxDelay != 0)
			{	try
				{	
					long sleepTime = Math.round(Math.random() * maxDelay );
					log.debug("Inserting " + sleepTime + "ms input delay.");
					Thread.sleep( sleepTime );
				}
				catch (InterruptedException e)
				{	fail("Interrupted from sleep.");
				}
			}
		}
		
		int read = (out != null) ? out.read() : inputReader.read() ;
		
		if( read