用AOP/PostSharp进行单元测试

本文关键字:单元测试 PostSharp AOP | 更新日期: 2023-09-27 18:17:36

我正在尝试使用PostSharp来实现一个安全方面,以便在我的存储库层中应用方法级授权检查。

概念概述如下。

然而,这些授权检查在单元测试期间妨碍了工作,使它们更像是集成测试。

将这些隔离为单元测试的最佳方法是什么,基本上忽略/模拟安全方面,以便我可以只测试实际的类行为,而不需要初始化一堆安全信息?

AOP与单元测试有内在冲突吗?

用AOP/PostSharp进行单元测试

首先回答你的第二个问题,不,AOP与单元测试在本质上并不冲突。通常我认为最好是分别对方法和方面进行单元测试。

在您的情况下,有几个选项。

最简单的方法是使用单元测试设置方法确保线程具有所需的权限。

如果你不想那样做,有两种方法你可以在你的单元测试中分离东西。第一种方法是从方法中提取应用安全方面的所有代码到单独的方法中,如下所示:

[SecurityAspect]
void DoSomething()
{
    DoSomethingInternal();
}
void DoSomethingInternal()
{
    // this is the real code
}

然后你可以针对所有不安全的"内部"方法运行单元测试——测试其中的逻辑而不用担心安全性。

第二种方法是将模拟权限测试器注入到方面本身。为了能够做到这一点,您必须定义一个单独的类和接口来执行测试安全性的实际逻辑,类似于以下内容(假设它是您传递来验证安全性的线程):
public interface IPermissionsChecker
{
    bool HasPermissions(Thread thread);
}

这是您的活动系统的权限检查器:

public class RealPermissionsChecker : IPermissionsChecker
{
    public bool HasPermissions(Thread thread)
    {
        // do your real work here
    }
}

这是你将在单元测试中使用的

public class MockPermissionsChecker : IPermissionsChecker
{
    public bool HasPermissions(Thread thread)
    {
        return true;
    }
}

现在你需要像这样定义你的方面:

public class SecurityChecker : OnMethodBoundaryAspect
{
    IPermissionsChecker _checker;
    public override void OnEntry(MethodExecutionArgs args) 
    { 
        if (!_checker.HasPermissions(Thread.CurrentThread))
            throw new SecurityException("No permissions");
    }
}

唯一剩下的问题是需要将正确的权限检查器注入到方面。

我以前做过的稍微有点粗糙的方法是使_checker成为静态字段,并提供一个静态方法来初始化它:
public class SecurityChecker : OnMethodBoundaryAspect
{
    private static IPermissionsChecker _checker;
    public static void InjectChecker(IPermissionsChecker checker)
    {
        // best put some code here to make sure this is only called once,
        // as well as doing thread synchronization
        if (_checker == null)
            _checker = checker;
    }

事实上,InjectChecker是静态的意味着你可以从你的应用启动(或单元测试启动)代码访问它。我怀疑单元测试纯粹主义者会不赞成这一点——你必须确保在应用程序启动时调用它,但我认为这是将检查器注入方面的最简单方法,避免了代码的其余部分不能直接访问方面实例的事实。

更复杂的方法是在方面中重写RunTimeInitialize()——这个方法在方面初始化时由PostSharp调用。你可能会这样做:

    public override void RuntimeInitialize(MethodBase method)
    {
        base.RuntimeInitialize();
        this._checker =PermissionsCheckerProvider.Current.GetChecker();
    }

你会发现这需要你定义另一个类:

public class PermissionsCheckerProvider
{
    // make sure you set this at app startup, either to the mock or to the real checker
    public static PermissionsCheckerProvider Current { get; set;}
    public IPermissionsChecker GetChecker()
    {
    }
}

这种方法保证了方法将在正确的时间尝试初始化,但是在方面尝试初始化之前,您需要确保提供了适当的当前提供者。所以我个人可能会选择第一种方法来保持事情的简单。

这里有一些关于依赖注入和RuntimeInitialize的讨论。https://codereview.stackexchange.com/questions/20341/inject-dependency-into-postsharp-aspect

两个广泛回答你问题的链接:

  • 关于该主题的网络研讨会记录,有两种不同的观点:Matt Groves和我自己
  • 关于测试方面的官方PostSharp文档

如果在单元测试中使用Typemock,则可以使用

MyAspect myAspectMock = Isolate.Fake.Instance<MyAspect>(Members.MustSpecifyReturnValues);
Isolate.Swap.AllInstances<MyAspect>().With(myAspectMock);

这允许您控制使用哪些方面的测试,哪些方面不使用,允许您测试方法本身,并应用建议。

想必其他mock框架也会有类似的机制