问题
My apologies in advance for a terrible title- suggestions welcome!
I've been reading about DI and AOP, and I think I grasp the basics; at least for the canonical example of adding logging.
I would like to apply this to the test cases we've been creating in NUnit e.g. be able to automatically add entry/exit logging for all test case methods and any 'helper methods' that they call. (And I'm not tied to NUnit- if it's easier in another framework, please let me know.)
NOTE- this is not about the subject under test; I want to apply these techniques to the testcases themselves.
It's pretty clear how to do this using PostSharp - it's their First example. However I don't want to add the handling of their licensing to our project for just this experiment.
All the other references I've found to AOP for C# are based around (dynamic) Interceptors provided by IoC container implementations such as CastleWindsor, Unity, Spring.Net, ... They all have a common problem in this case: you need a piece of setup-code that creates the proxy for the object you want to add interceptors to. (I did originally think this code would also have to create an IoC container, but I see I'm wrong there.)
But I can't see where this setup code would go for nUnit testcases.
The options I've come up with, and their problems:
- Have the testfixture classes constructor create a proxy to itself. Won't work, due to recursion (consumer asks for thing, thing tries to return proxy to thing, proxy tries to create thing... from reading this StackOverflow question)
- Roll my-own reflection based magic (which would be a big undertaking for me)
- have the constructor wrap all methods in the testfixture class and return this 'wrapped' object (not sure if this it's possible for a constructor to do this)
- use a static constructor on the testfixture to do this magic (assuming you can dynamically wrap the methods of the class in place.)
- do something at the module level using a module cctor (via Einar Egilsson's InjectModuleInitializer) and wrap all methods in all classes with logging.
- The simplest: some kind of factory for instantiating the testcases (NOT parameters for the tests), from which I could use one of the IoC Proxy Generators
- For nUnit: the only means I can find to do this is to create a custom AddIn. Advantage- may not break integration with ReSharper. Disadvantage- deploying to all devs machines, especially on updates to NUnit. Are there other ways of doing this for nUnit?
- For MbUnit: looks like it treats testcases as first class values, and this is straight-forward. Advantage: easy to deploy to all developers. Disadvantage: tests aren't going to show up in Resharper. Side note: how to handle setup and teardown.
Have I missed anything in my options and conclusions?
Are there any simpler means of doing this that I've missed?
回答1:
Aspect Oriented Programming is not only about using dynamic proxies (interception) or post compilation code weaving (PostSharp). AOP is mainly about adding cross-cutting concerns. Using dynamic proxies is one way of adding cross-cutting concerns. Code weaving is another way of doing this. But there is another, IMO better, way of adding cross-cutting concerns.
Instead of using dynamic proxies or code weaving, let the design of your application lead you. When you design your application using the right abstractions, it would be easy to add cross-cutting concerns using decorators. You can find examples of systems that are designed using proper abstractions here and here.
Those articles describe how you define cross-cutting concerns using decorators. When you designed the system in this way, you can test the implementation of a cross-cutting concern separately from the rest of the code. This will be easy when using the right abstractions.
When you do this, there is no need to do anything special in your unit tests. No need for code weaving, no need to run your DI container in your test to build up objects for you. You can test your application logic without any cross-cutting concern to be in the way. You can test every little piece in isolation and put all pieces together it in your application’s the Composition Root.
回答2:
Here a sample using Puresharp Framework (by installing IPuresharp + Puresharp nugets on test project.)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using NUnit.Framework;
using Puresharp;
namespace TEST
{
/// <summary>
/// Class to test with NUnit
/// </summary>
public class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
}
/// <summary>
/// Test class for calculator with a simple test.
/// </summary>
[TestFixture]
public class CalculatorTest
{
/// <summary>
/// Static constructor (class constructor) to attach aspect to test method.
/// </summary>
static CalculatorTest()
{
//Instantiate my custom aspect.
var myBasicAspect = new MuCustomAspect();
//Attach my custom aspect to my custom pointcut
myBasicAspect.Weave<MyCustomPointcut>();
}
[Test]
public void ShouldAddTwoNumbers()
{
var _calculator = new Calculator();
int _result = _calculator.Add(2, 8);
Assert.That(_result, Is.EqualTo(10));
}
}
/// <summary>
/// Pointcut to identify methods group to weave (here test methods of CalculatorTest).
/// </summary>
public class MyCustomPointcut : Pointcut
{
override public bool Match(MethodBase method)
{
return method.DeclaringType == typeof(CalculatorTest) && method.GetCustomAttributes(typeof(TestAttribute), true).Any();
}
}
/// <summary>
/// Défine an aspect.
/// </summary>
public class MuCustomAspect : Aspect
{
public override IEnumerable<Advisor> Manage(MethodBase method)
{
//Aspect will advice method on boundary using MyCustomAdvice.
yield return Advice.For(method).Around(() => new MyCustomAdvice());
}
}
/// <summary>
/// Define an advice.
/// </summary>
public class MyCustomAdvice : IAdvice
{
public MyCustomAdvice()
{
}
public void Instance<T>(T value)
{
}
public void Argument<T>(ref T value)
{
}
public void Begin()
{
}
public void Await(MethodInfo method, Task task)
{
}
public void Await<T>(MethodInfo method, Task<T> task)
{
}
public void Continue()
{
}
public void Return()
{
}
public void Return<T>(ref T value)
{
}
public void Throw(ref Exception exception)
{
}
public void Throw<T>(ref Exception exception, ref T value)
{
}
public void Dispose()
{
}
}
}
来源:https://stackoverflow.com/questions/13462612/how-to-use-aop-from-di-frameworks-with-c-sharp-testframeworks-not-for-the-item