Using AspectJ For Testing Legacy Code

Recently I brought up the topic of Aspect Oriented Programming (AOP) at work. I argued that it does have uses beyond mere tracing and psuedo-logging. In fact, it’s all over the place.

Just to get familiar with it again I looked at how it could be used on a test scenario I had. A class under test directly uses a Singleton collaborating class to invoke a service call, in other words non-dependency injection. This is similar to a class using “new” to create objects it depends on.

A requirement of unit testing is the isolation of the class from its collaborators (to a pragmatic degree). Since the Singleton class has a private constructor it can’t be subclassed or proxied (even with cglib via Mock frameworks).

I created a simplified version of the scenario to show how “easy” it is to use AOP. First the class under test class in listing one:

public class ClassUnderTest {
	/**   	 */
	public void service(){
		Collaborator.getInstance().speak();
	}

	/**  	 */
	public static void main(String[] args) {
		ClassUnderTest main = new ClassUnderTest();
		main.service();
	}

} // end ClassUnderTest

And, the collaborator singleton class is in listing two:

public class Collaborator {
	private static final Collaborator instance = new Collaborator();

	/**   */
	private Collaborator(){
	}

	/**  */
	public static Collaborator getInstance(){
		return instance;
	}

	/**  */
	public void speak(){
		System.out.println("Hello!");
	}

} // end class Collaborator

The result of a compile and run is:

src>javac -d ..\bin ClassUnderTest.java Collaborator.java
src>cd ..\bin
bin>dir
bin>java ClassUnderTest
Hello!

Now, the scenario goal is to change the speak() method to instead output "Goodbye!". The aspect in listing three accomplishes this. It defines two pointcuts, cut() that specifies the class under test, and service() that identifies the target speak() method call in the collaborator object. The around() advice allows us to substitute our own replacement 'method'. In the 'around' advice, using proceed() would let the original method execute. Note the terminology used here is not Aspectively correct.

public aspect TestAspect {

	pointcut cut() :
		execution(public void ClassUnderTest.service()) &&
		!within(TestAspect);

	pointcut service() : cflow(cut())
	    && call(public void Collaborator.speak());

	void around() : service() {
		System.out.println("Goodbye!");
		//proceed();
	}

} // end class TestAspect

Compiling:

.....\src>\java\aspectj1.6\bin\ajc -cp "c:\java\aspectj1.6\lib\aspectjrt.jar;c:\java\aspectj1.6\lib\aspectjtools.jar;c:\java\aspectj1.6\lib\aspectjweaver.jar" -sourceroots .
Compiler took 958ms
 

Running:

....\src>cd ..\bin

....\bin\java -cp "c:\java\aspectj1.6\lib\aspectjrt.jar;c:\java\aspectj1.6\lib\aspectjtools.jar;c:\java\aspectj1.6\lib\aspectjweaver.jar;." ClassUnderTest
Goodbye!

Success!

Of course, our aspect in real life would not "do" the actual required test actions, but instead delegate to a test class or stub to do so. This is especially important if the collaborator object has many methods that must be advised. At first I was trying to do this directly, swapping out the use of the Singleton Collaborator class using static crosscutting, to another dynamically generated stub or mock class. Though with AspectJ you can do some powerful stuff, the private constructor puts a kink in all that.

Left to the reader is actually making this work within an actual Unit Testing framework like JUnit.

Updates

  • 30 Oct 2010:  Another approach which in many cases may be better is using the JMockit framework.  It does work with real Singletons and other cases where AspectJ would be the only recourse.
  • 15 Dec 2010: If you can change the source code, a very simple test pattern is to extract the troublesome code lines into a method. Now that method can more easily be mocked.

Further Reading

This post is duplicated in new blog: Using AspectJ For Testing Legacy Code
JMockit, http://code.google.com/p/jmockit/

"Advanced Mocking: Capturing State with Answer and Captors", http://canoo.com/blog/?p=1592#captcha_input

Aspect Oriented Software Development

"Why use an AOP language when other languages can now do Aspects?", Josef Betancourt, http://goo.gl/r1YB

"Next steps with aspects", http://www.ibm.com/developerworks/java/library/j-aopwork16/

"Testing legacy code", Elliotte Rusty Harold, http://www.ibm.com/developerworks/java/library/j-legacytest.html

AspectJ, http://eclipse.org/aspectj/

Nicholas Lesiecki, "Test flexibly with AspectJ and mock objects", 01 May 2002, http://www.ibm.com/developerworks/java/library/j-aspectj2/

"Approaches to Mocking", Simon Stewart, http://onjava.com/pub/a/onjava/2004/02/11/mocks.html?page=1

"Use AOP to maintain legacy Java applications", http://www.ibm.com/developerworks/java/library/j-aopsc2.html

"Static crosscutting", http://eclipse.org/aspectj/doc/released/progguide/semantics-declare.html

Qi4j, http://www.qi4j.org/ Fascinating project and concepts, IMHO.

Russ Miles, AspectJ Cookbook, O'Reilly Media, Inc., 2005

AspectJ In Action, Ramnivas Laddad, Manning, http://manning.com/laddad2/

"Multi-threaded Testing with AOP Is Easy, and It Finds Bugs!", http://www.springerlink.com/content/c8m3g6v2cp49xcg6/

"Making AspectJ development easier with AJDT", http://www.infoq.com/articles/aspectj-with-ajdt

Bodden, Eric; Havelund,Klaus. "Racer: Effective Race Detection Using AspectJ", http://www.havelund.com/Publications/issta2008.pdf

About these ads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: