4

我提前为糟糕的标题道歉 - 欢迎提出建议!

我一直在阅读有关 DI 和 AOP 的内容,并且我认为我掌握了基础知识;至少对于添加日志记录的规范示例。

我想将此应用于我们在 NUnit 中创建的测试用例,例如能够为所有测试用例方法和它们调用的任何“辅助方法”自动添加进入/退出日志记录。(而且我不依赖于 NUnit——如果在另一个框架中更容易,请告诉我。)

注意——这与被测对象无关;我想将这些技术应用于测试用例本身。

很清楚如何使用PostSharp做到这一点——这是他们的第一个例子。但是我不想仅仅为了这个实验将他们的许可处理添加到我们的项目中。

我发现的所有其他关于 AOP for C# 的参考都是基于 IoC 容器实现(如 CastleWindsor、Unity、Spring.Net 等)提供的(动态)拦截器......在这种情况下它们都有一个共同的问题:你需要一个一段设置代码,为要添加拦截器的对象创建代理。(我最初确实认为这段代码也必须创建一个 IoC 容器,但我发现我错了。)

但是我看不到这个设置代码将用于 nUnit 测试用例。

我提出的选项及其问题:

  1. 让 testfixture 类构造函数为其自身创建一个代理。由于递归(消费者要求事物,事物试图将代理返回到事物,代理试图创建事物......从阅读这个 StackOverflow 问题
  2. 滚动我自己的基于反射的魔法(这对我来说是一项艰巨的任务)
    1. 让构造函数包装 testfixture 类中的所有方法并返回这个“包装”对象(不确定构造函数是否可以这样做)
    2. 在 testfixture 上使用静态构造函数来实现这一点(假设您可以动态地将类的方法包装到位。)
    3. 使用模块 cctor(通过Einar Egilsson 的 InjectModuleInitializer)在模块级别做一些事情,并使用日志记录包装所有类中的所有方法。
  3. 最简单的:某种用于实例化测试用例的工厂(不是测试的参数),我可以从中使用其中一个 IoC 代理生成器
    1. 对于 nUnit:我能找到的唯一方法是创建一个自定义 AddIn。优势 - 可能不会破坏与 ReSharper 的集成。缺点 - 部署到所有开发机器,尤其是在更新 NUnit 时。是否有其他方法可以为 nUnit 执行此操作?
    2. 对于 MbUnit:看起来它将测试用例视为第一类值,这是直截了当的。优点:易于部署给所有开发人员。缺点:测试不会出现在 Resharper 中。旁注:如何处理 setup 和 teardown

我是否遗漏了我的选择和结论中的任何内容?

有没有我错过的更简单的方法?

4

2 回答 2

1

面向方面的编程不仅仅是使用动态代理(拦截)或编译后代码编织(PostSharp)。AOP 主要是关于添加横切关注点。使用动态代理是添加横切关注点的一种方式。代码编织是执行此操作的另一种方式。但是,还有另一种更好的 IMO 方式来添加横切关注点。

与其使用动态代理或代码编织,不如让应用程序的设计引领您。当您使用正确的抽象设计应用程序时,使用装饰器添加横切关注点会很容易。您可以在此处此处找到使用适当抽象设计的系统示例。

这些文章描述了如何使用装饰器定义横切关注点。当您以这种方式设计系统时,您可以将横切关注点的实现与其余代码分开测试。当使用正确的抽象时,这将很容易。

当你这样做时,你不需要在你的单元测试中做任何特别的事情。无需代码编织,无需在测试中运行 DI 容器来为您构建对象。您可以测试您的应用程序逻辑,而不会妨碍任何横切关注点。您可以单独测试每个小部分,并将所有部分放在应用程序的Composition Root中。

于 2012-11-20T10:20:55.783 回答
0

这是一个使用Puresharp Framework的示例(通过在测试项目上安装IPuresharp + Puresharp nugets。)

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()
        {
        }
    }
}
于 2018-12-07T11:15:37.277 回答