Gael Fraiteur joins this final episode in the AOP in .NET series to give his differing opinion about the relevancy of "thin" aspects and speaks about a set of practices that can be used to create efficient tests of aspects.
Q: What would be the best way to inject the cache provider VIA an IOC container? (e.g. Autofac/Unity)
A: Whatever container you wish to use is fine. In my example I just new them up directly but in reality you shouldn't do that. In structure map you could say:
if (!On) return;
As long as you're not tying it to a specific implementation, you're just asking for something that implements the interface. This is known as the service locator pattern.
Q: Gael, you mentioned in your presentation that one can't test an aspect in isolation of a target. Isn’t that exactly what Matthew just showed us?
A: Gael – yes, but Matt showed it in a very specific case using compiler internals to do it by instantiating the execution arguments that are being passed to the aspect. In the case of OnMethodBoundary Aspects, this is very simple to do, but if there are changes in methods or invocations it can become quite complex.
Matt showed how to isolate the aspect completely from the aspect framework with the notion of thin aspects but to me the cost of doing so is huge. There is the abstraction that is added on top of the aspect which isn’t justified because test coverage is worsened and the meta data is part of the input. Just as you wouldn’t test a method without an argument, you wouldn’t test an aspect without a target.
Matt – what I tested in isolation wasn’t an aspect but rather the advice or the actual logic of the aspect.
Gael – Matt’s approach may work in simple cases but I would be curious to see how it works in complex aspects, including aspect combinations.
Q: Why would you use a static IOC resolution instead of using the service locator with each call of "OnEntry"? (in the logging example)
A: This is a matter of optimization. Logging is very performance sensitive, because it’s invoked at a very high frequency, so I would avoid resolving dynamically but this is entirely up to you. In documentation, I show several approaches where the service locator returns a delegate that may be re-evaluated each and every time (or not, depending on your preference).
There is no general rule or best practice here, it depends on what you want to achieve in each situation and the tradeoffs between architecture and performance.
Q: Gael, you mentioned in your presentation that you can’t unit test build-time logic because PostSharp performs code weaving at compile-time. For a consumer of a class or a framework, why would I be concerned on how the deliverables are made instead of how they are used?
A: It depends on who the consumer is. In large teams, you’re delivering aspects to the rest of the team and not the end customer. With some of our largest customers 10% of the team build aspects and the other 90% consume the aspects. In those cases, if you want your team to be more productive then you need to check that the aspect has correct build-time behavior. Then the problem is: how to test validation.
If however your aspect never emits build-time errors, then run-time testing may be sufficient.
Q: Couldn't you unit test the aspects advice the way Matthew suggests, and then integration test the combination and ordering of method calls of the aspects the way Gael suggests? Do these techniques really need to be mutually exclusive?
A: Gael – it depends on what your objective is. If your objective is to decouple the framework from the aspect, then Matt’s approach may be useful. However, if the objective is to do good testing in as little time as possible then why would I add complexity and decouple the framework from the aspect? I don’t see the benefit in terms of architecture and time-savings.
Matt – Gael and the PostSharp team take a different approach than I would because they’re writing aspects for the general public to consume and not just my team.
Q: In the case of injected concerns/dependencies for aspects, would you use a fake resolver?
A: Matt – in my case the aspect was thin enough so I didn’t have to use a fake resolver. What I did instead was to pass-in mock objects directly, injecting them myself.
Gael – you should have your resolver work differently, depending on the context. In production, you would involve your real resolver and in testing you would add rules. and according to the context to which the aspect is being applied, it would return a different implementation of the service. these are all options to consider.
Q: Have any of you used something like nCrunch with PostSharp?
A: Matt – I’ve used nCrunch before, it’s a great test runner tool, and haven’t had any problems with using it and PostSharp together. However it’s been a while since I last used it.
Many thanks to Matthew Groves for the wonderful 5-part series. We wish him much success with his new book, Aspect-Oriented Programming in .NET, out this month. You can see Matt speak and catch up with him in person this summer at Louisville .NET Meetup Group and CodeStock.
Alex Papadimoulis of The Daily WTF fame will join us on June 27th for the next PostSharp Live Webinar to talk about NuGet, deployment management issues, and private repositories as a possible solution.
More on that coming very soon.