Friday 18 June 2010

C-eeing Thru Your Code


Ladies and gentlemen, let me introduce you to this exciting library, CThru. (I planned to add several jokes here, but changed my mind). This is an open source AOP framework built on top of Typemock Isolator. Recently I added several improvements to the built-in aspects, and thought I might write a blog post about it.

CThru requires Typemock Isolator to be installed on the target box, so you probably want to use it in your test projects. CThru does not require you to modify the original code, nor the compiled binaries, so you're free to use it on, say, BCL libraries (except for mscorlib). It works by intercepting method calls and add or change their behavior. Each aspect is responsible for deciding which calls to intercept by implementing the ShouldIntercept method. You can make a decision based on the target instance, class name, or method name. For example, you could apply it to all implementors of a particular interface, or for all types in a particular namespace.

In order to make it work, you add all necessary aspects by calling CThruEngine.AddAspect(), then call CThruEngine.StartListening(). The cleanup is done by calling CThruEngine.StopListeningAndReset().

So, how do you write your own aspect? First, you inherit from the abstract Aspect class, and implement the ShouldIntercept method. As I said before, it determines the condition for which the aspect is applied. To add specific behavior, you should override the MethodBehavior or ConstructorBehavior methods. By default, your code is run before the actual method is invoked. You can also skip the original method, return custom value, or throw an exception by manipulating the properties of the DuringCallbackEventArgs valued argument. If you want to add some behavior after the original method is invoked, you can execute the Aspect.CallOriginalMethod method (don't forget to set e.MethodBehavior = MethodBehaviors.SkipActualMethod so that it isn't called twice).

There are aspects that are generic in nature: the behavior is the same, but they are applied in different situation, so it doesn't make sense to hardcode the ShouldIntercept method. For example, the Stub aspect just ignores the method call, but which call it ignores should be determined by the code that uses the aspect. So, there's a convenient base class called "CommonAspect". It makes it possible to pass the intercepting decision in the constructor argument. For example, if you want to ignore all methods with names starting with "My", you write:
CThruEngine.AddAspect(new Stub(info => info.MethodName.StartsWith("My")));
That said, let's review some of the built-in aspects:
  • SkipAllByTypeName -- very simple aspect, designed to illustrate how aspects should be written. Skips all method calls of classes that contain the specified string in their names.
  • MissingMethodAspect -- implements Ruby-like behavior in VB.Net (when you try and execute a non-existent method, it invokes the method called "method-missing" instead).
  • TraceAspect -- traces all intercepted calls to the console (or the supplied TextWriter instance), optionally with the stack trace.
  • TraceResultAspect -- same but traces the results as well. Due to the CThru limitation, all calls invoked by the traced call are not traced (unlike with TraceAspect).
  • DebugAspect -- if you have attached the debugger, it pauses the execution before calling the intercepted method. Very useful for investigating the state of the system at a particular point, when you don't have access to the code at that point.
  • Stub -- just ignore the call and return null. ToDo: implement returning a custom value.
Several projects that are built on top of, or developed using, CThru:
  • SilverUnit, a framework for testing Silverlight applications. Bundled with CThru.
  • Balder. Managed 2D and 3D graphics engine, targetting Silverlight, Xna and OpenGL.
  • Ivonna, an Asp.Net unit and integration testing tool.

So, whether you just need some exploratory testing, or building a domain-specific test framework, CThru can save you a lot of effort.