Quantcast
Channel: T. C. Mits 108 » java
Viewing all articles
Browse latest Browse all 36

Java testing using XStream, AspectJ, Vsdf

$
0
0

Problem
A unit or integration test may require the creation of complex object structures. This is even more problematic in legacy systems that were not created with testability in mind. Faced with this, it may seem better in the short term to avoid programmatic tests and continue to rely on traditional test techniques.

Solution
A possible short term approach is to use object serialization to capture the required objects as they are used in the actual system. Then a test can rebuild the objects and use them in various tests. We will not elaborate on the shortcomings of this approach.

XStream
Their are many approaches in Java that can be used for object serialization: JAXB, JavaBean serialization, and so forth. All of them have issues. For example, many of them require that the object to be serialized conform to certain requirements, like the JavaBean specification. If the objects don’t there are ways around it, but this quickly becomes complex, not only must the top level object be ‘handled’ but then its nested object graph. The XStream library does not have these requirements and so can probably handle a large percent of the use cases.

Example Test
In listing 1 below is a unit test using XStream. A class called XApp contains a scenario1 method that we wish to test. To invoke scenario1, a lot of data must be created and structured into a required object hierarchy, here we just use a simple map to represent that. Thus, the test creates an object from an XStream XML serialization and then invokes the scenario1() method of the system under test.

Listing 1, test
package com.octodecillion.testing;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;

import java.io.File;
import java.io.IOException;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import com.octodecillion.testing.XApp.Inventory;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.StaxDriver;

/**
 * @author jbetancourt
 *
 */
@RunWith(JUnit4.class)
public class InventoryTest {
	/**
	 * @throws IOException 
	 * 
	 */
	@SuppressWarnings("boxing")
	@Test
	public void scenario1_test() throws IOException {
		Inventory inventory = (Inventory) new XStream(new StaxDriver())
				.fromXML(new File(
				"inventory-scenario1.xml"));

		XApp app = new XApp();
		app.setInventory(inventory).scenario1();
		assertThat(app.getInventory().stock.size(), is(1));
	}

}

AOP
Since there are no mappings or modifications to objects, serializing with XStream takes two lines of code. We can insert this source code (or via a utility) where we need it in the system under test to capture a state for future testing. However, this requires that we litter the code with these serialization concerns. We can remove them after we capture the object, of course.

An alternative is to use Aspect Oriented Programming. With AOP we can ‘advise’ at ‘joinpoints’ to capture objects. This can be done with Load-Time Weaving. The original source is unmodified and to recapture the same objects we just rerun the system and reapply AOP LTW.

In listing 2, the AspectJ AOP Java framework is used to create an aspect to capture the data object at the setScenarioData() method in the class under test, XApp.

Listing 2, object capture
package com.octodecillion.testing;

import java.io.File;
import java.io.FileWriter;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.StaxDriver;

/**
 * Aspect to allow XStream streaming of application data object.
 * 
 * @author jbetancourt
 *
 */
public aspect XStreamObject {
	/**
	 * Capture the App inventory object during creation of data 
	 * and stream to file.
	 * 
	 */
	@SuppressWarnings("javadoc")
	after(final XApp app) : 
		execution( protected void setScenarioData()) && 
		!within(XStreamObject) && target(app) {
		
		try {
			FileWriter writer = new FileWriter(
					new File("inventory-scenario1.xml"));
			new XStream(new StaxDriver()).toXML(app.getInventory(), writer);			
			writer.close();			
		} catch (Exception e) {
			e.printStackTrace();
		}		
	}	
}

Storing using Vsdf
One problem with using serialization for tests is where to store these. If we have a complex app, which we do or else why go through all this, there can be a lot of objects and these can be in various states depending on the test scenario requirements.

In prior posts Simple Java Data File and very simple data file I presented a concept for just this scenario. With Vsdf, multiple serializations can be stored in one file. Thus we can store all streamed object in one file, or various serialized states of an object type can be stored per Vsdf file. Some advantages of this approach is the reduction in test files, ability to change or update individual serializations.

Example Class to test
In listing 3 below is the example class to test. It is more of a “Hello World” type of app.

Listing 3, target class to test
package com.octodecillion.testing;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * Simple XStream example.
 * 
 * @author jbetancourt
 *
 */
public class XApp {

	private Inventory inventory;
	
	/**
	 * The scenario that needs testing.
	 */
	public void scenario1() {
		// do the business case here ...
	}

	/** default constructor */
	public XApp() {
		setScenarioData();
	}

	/**
	 * The Application entry point.
	 * @param args
	 * @throws IOException 
	 */
	public static void main(final String[] args) throws IOException {
		new XApp();
	}

	/**
	 * set up the data for scenario1
	 */
	protected void setScenarioData() {
		inventory = new Inventory();
		inventory.add("322", new Vehicle() {
			@Override
			public int getNumWheels() {
				return 4;
			}

		});
	}

	/**
	 * @author jbetancourt
	 *
	 */
	public class Inventory {
		/**		 */
		Map<String, Vehicle> stock = new HashMap<String, Vehicle>();

		/**
		 * @param id
		 * @param veh
		 */
		public void add(final String id, final Vehicle veh) {
			stock.put(id, veh);
		}

	} // end class Inventory

	/**
	 * @author jbetancourt
	 *
	 */
	public interface Vehicle {

		/**
		 * @return num of wheels
		 */
		public int getNumWheels();

	} // end Vehicle

	/**
	 * @param inventory
	 * @return the app object
	 */
	public XApp setInventory(final Inventory inventory) {
		this.inventory = inventory;
		return this;
	}

	/**
	 * @return the inventory
	 */
	public Inventory getInventory() {
		return inventory;
	}

} // end class XApp

Issues
Sounds great but what happens when a class that was serialized is modified, like gets new fields? How do we handle versioning and so forth?

Links

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

Viewing all articles
Browse latest Browse all 36

Trending Articles