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.

There is a more recent post regarding this topic: Use AOP Aspects as Mocks in JUnit tests?

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>javaaspectj1.6\bin\ajc -cp "c:\javaaspectj1.6\lib\aspectjrt.jar;c:javaaspectj1.6\lib\aspectjtools.jar;c:\javaaspectj1.6\lib\aspectjweaver.jar" -sourceroots .
Compiler took 958ms
 

Running:

....src>cd ..\bin

....bin\java -cp "c:\javaaspectj1.6\lib\aspectjrt.jar;c:\javaaspectj1.6\lib\aspectjtools.jar;c:\javaaspectj1.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 library.  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

  • Use AOP Aspects as Mocks in JUnit tests?
  • 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
  • Similar Posts:

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

    One thought on “Using AspectJ For Testing Legacy Code”

    Leave a Reply

    Your email address will not be published. Required fields are marked *