Using load-time AOP to help testing and debugging

After reading this blog ‘Practical Introduction into Code Injection with AspectJ, Javassist, and Java Proxy‘, it reminded me how handy AOP tools like AspectJ are, in testing and debugging applications – particularly on servers.

How many times have you encountered a problem or bug in staging (or common test or reference) servers that you can’t reproduce in your own dev server? Ideally deployments should be consistent and reproducable so that this should never happen, but unfortunately in real projects that is not always the case.

Sometimes various issues can cause these differences:

  • manual configurations to the build process
  • continuous integration not used rigorously
  • different environmental settings on different server instances
  • different external services used in development, as opposed to a staging environment

Factors like these may lead to inconsistent builds or differing behaviour in different server environments.

I’ve found that using load-time weaving with AspectJ can be useful testing and debugging the application code on a staging server, without requiring any source code changes. The good thing is that because it doesn’t change the actual build artifacts on the file system (of course the code is changed when the affected classes are loaded!), you can remove the load-time weaving any time and revert back to the original code without having to do another build.

Here are some examples of what can be done to assist testing and debugging on a server that is not directly controlled by the developer. I’m assuming here that you are able to implement load-time weaving on the server.

Also if you are not familiar with AOP and AspectJ, else please read the AspectJ documentation or a book on AspectJ, such as the excellent AspectJ in Action.

Tracing

The generic introductory example for demonstrating how useful AOP is, seems to be tracing. The ability to add logging code to trace (program flow, method parameters and return value, etc) is very handy for debugging, particularly when using pointcuts to narrow the scope of the tracing to the sections of code that are problematic.

Here is a simple example with some test classes:

public class Customer {
  private String name;
  private String phone;
  private String address;

  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public String getPhone() {
    return phone;
  }
  public void setPhone(String phone) {
    this.phone = phone;
  }
  public String getAddress() {
    return address;
  }
  public void setAddress(String address) {
    this.address = address;
  }
}

public class AccountService {

  public boolean addCustomer(Customer customer, String accountId, boolean isNewCustomer)
  {
    // real code to add customer to an account would be added here

    return true;
  }
}

For the tracing code, you can use the examples from the AspectJ programming guide or the book AspectJ in Action.

Then in a trace aspect class, use a pointcut to trace the code we’re interested in (in this case AccountService.addCustomer()).

pointcut traceOperations() : execution(* trace.blogexample.AccountService.addCustomer(..));

When the code is run, you might get some tracing, showing that the code was run and the parameters that were passed to the method in the pointcut.

Enter  [trace.blogexample.AccountService.addCustomer]
  [ This: trace.blogexample.AccountService@6262937c]
  [Args: (trace.blogexample.Customer@35c0e45a,ABC123,true)]
Exit  [trace.blogexample.AccountService.addCustomer]

Even more useful is if you combine the logging code with reflection to trace not only simple values, but also the contents of objects that you are interested in.
For the above example you might get tracing that shows the fields of objects passed to the method as parameters.
Hopefully the info from the tracing may be helpful in fixing a bug or problem.

Enter  [trace.blogexample.AccountService.addCustomer]
  [ This: trace.blogexample.AccountService@1d5a0305]
  [Args: (trace.blogexample.Customer@377653ae[
  name=John Smith
  phone=1234567890
  address=1 Test Street
  ],java.lang.String@54cbf30e[
  value={A,B,C,1,2,3}
  offset=0
  count=6
  hash=1923891888
  ],java.lang.Boolean@442a15cd[
  value=true
  ])]
Exit  [trace.blogexample.AccountService.addCustomer]

As I’ve mentioned, you can target where the want the tracing to occur:

  • by setting the pointcut to trace in details the areas of code that are (or that you think are) causing problems,
  • where exceptions are being thrown (using an After throwing advice)

You can also build on the AOP tracing to do other useful things, for example I have an open source project (TestDataCaptureJ) that uses tracing to capture data to use in unit testing.

Data Validation

Instead of just tracing, you can go further by doing some data validation as well.

For instance you can insert some code to validate the parameters for a business method, and log any validation errors.

Another way is to use tools, such as Contract4J, to test compliance (checking for preconditions, postconditions and invariants in the business requirements). Use load-time weaving to hook in the Contract4J configuration for business methods that you want to test.

Method Replacement

Another technique I sometimes use is to replace methods invokations in the app with test code.
This involves creating an Around advice for a pointcut that includes a method we want to replace.
Then inside the Around advice you can:
1. run another method altogether

So instead of just invoking the original method …

Object returnValue = joinPoint.proceed(args);

invoke another method with the same return type.

Object returnValue = newMethod(args);

2. run with different parameters

Invoke the original method, but pass in different parameter values (of course the parameter types must match the method signature)

Object returnValue = joinPoint.proceed(newArgs);

So using method replacement techniques you can do things like:

  • inject your own data to test a particular scenario
  • proxy external calls, e.g. for a method that uses an external service, replace with a method that returns some test data we can use for diagnosis

Summary

These are just some ideas that I have found useful when testing and debugging issues in app development. They can be easily applied to your development environment, but using load-time weaving with AOP also makes them useful when you encounter problems in a server that is not directly under your control.

Of course these techniques do not replace your standard tools and debuggers, it is just another tool in your toolbox.

2 thoughts on “Using load-time AOP to help testing and debugging”

  1. Pingback: pr9 dofollow blogs

Leave a Reply

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