How to use AOP from DI Frameworks with C # TestFrameworks? (NOT for the item to be verified)

My apologies in advance for the terrible name - suggestions are welcome!

I read about DI and AOP, and I think I understand the main points; at least for the canonical example of adding a journal.

I would like to apply this to the test cases that we created in NUnit, for example. be able to automatically add an I / O log for all test case methods and any "helper methods" they call. (And I'm not attached to NUnit - if it's easier in other frameworks, please let me know.)

NOTE. This does not apply to the test object; I want to apply these methods to the test windows themselves.

It's pretty clear how to do this using PostSharp - this is their first example. However, I do not want to add my licensing processing to our project just for this experiment.

All the other links I found for AOP for C # are based on (dynamic) hooks provided by IoC container implementations like CastleWindsor, Unity, Spring.Net, ... All of them have a common problem in this case: you need A snippet of installation code that creates a proxy for the object to which you want to add interceptors. (I initially thought that this code would also have to create an IoC container, but I see that I'm wrong there.)

But I canโ€™t see where this installation code will be used for nUnit test boxes.

The parameters I came up with and their problems:

  • Create a testfixture class constructor for yourself. It will not work, due to recursion (the consumer asks for a thing, the thing is trying to return the proxy server to the thing, the proxy is trying to create the thing ... from reading https://stackoverflow.com/a/4646778/ )
  • The role of my own reflection-based magic (which would be a big deal for me)
    • the constructor wraps all the methods in the testfixture class and returns this wrapped object (not sure if it is possible for the constructor to do this)
    • use the static constructor on testfixture to do this magic (assuming you can dynamically wrap class methods in place.)
    • do something at the module level using the cctor module (via Einar Egilsson InjectModuleInitializer ) and wrap all the methods in all classes using the log.
  • The simplest: some kind of factory for creating test module instances (NOT parameters for tests), from which I could use one of the IoC proxy generators
    • For nUnit: the only tool I can find is to create a custom AddIn . Advantage - Cannot disrupt integration with ReSharper. Disadvantage - Deployment on all devs machines, especially on NUnit updates. Are there other ways to do this for nUnit?
    • For MbUnit: it looks like it treats test indices as first-class values , and this is straightforward. Benefit: Easy deployment for all developers. Disadvantage: tests will not appear in Resharper. Note: how to deal with setup and shutdown .

Am I missing something in my options and conclusions?

Are there any easier ways to do this that I missed?

+4
source share
2 answers

Aspect-oriented programming is not only the use of dynamic proxies (interception) or re-compilation of compilation code (PostSharp). AOP basically consists in adding cross-cutting issues. Using dynamic proxies is one way to add cross-cutting issues. Code weaving is another way to do this. But there is another, IMO better, way to add cross-cutting issues.

Instead of using dynamic proxies or code weaving, let the design of your application lead you. When you develop an application using the right abstractions, it would be easy to add cross-cutting issues using decorators. You can find examples of systems that are created using the correct abstractions here and here .

These articles describe how you identify cross-cutting issues using decorators. When you design the system this way, you can test the implementation of the end-to-end task separately from the rest of the code. This will be easy when using the correct abstractions.

When you do this, there is no need to do anything special in your unit tests. There is no need to transcode the code, there is no need to run the DI container in your test to create objects for you. You can test your application logic without any cross-cutting issues. You can test every small part in isolation and put all its parts in your applications. Root composition .

+1
source

Here's a sample using the Puresharp Framework (by installing IPuresharp + Puresharp nugets in a 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() { } } } 
0
source

Source: https://habr.com/ru/post/1447075/


All Articles