如何对拦截器进行单元测试?

发布于 2024-11-03 18:05:33 字数 1921 浏览 4 评论 0原文

我想为拦截 Loggable 基类(实现 ILoggable)的拦截器编写一些单元测试。
Loggable 基类没有可调用的方法,仅用于由日志记录工具进行初始化。
据我了解,我应该:

  1. 模拟 ILoggableILogger
  2. 初始化日志记录工具
  3. 在其上注册我的拦截器
  4. 调用模拟的 ILoggable 的某些

方法问题是我的 ILoggable 接口没有可调用的方法,因此不会拦截任何内容。
在这里正确的做法是什么?
我应该手动模拟 ILoggable 并添加一个存根方法来调用吗?
另外,我也应该嘲笑容器吗?

我正在使用 Moq 和 NUnit。
编辑:
这是我的拦截器实现供参考:

public class LoggingWithDebugInterceptor : IInterceptor
{
    #region IInterceptor Members

    public void Intercept(IInvocation invocation)
    {
        var invocationLogMessage = new InvocationLogMessage(invocation);

        ILoggable loggable = invocation.InvocationTarget as ILoggable;

        if (loggable == null)
            throw new InterceptionFailureException(invocation, string.Format("Class {0} does not implement ILoggable.", invocationLogMessage.InvocationSource));

        loggable.Logger.DebugFormat("Method {0} called with arguments {1}", invocationLogMessage.InvokedMethod, invocationLogMessage.Arguments);

        Stopwatch stopwatch = new Stopwatch();
        try
        {
            stopwatch.Start();
            invocation.Proceed();
            stopwatch.Stop();
        }
        catch (Exception e)
        {
            loggable.Logger.ErrorFormat(e, "An exception occured in {0} while calling method {1} with arguments {2}", invocationLogMessage.InvocationSource, invocationLogMessage.InvokedMethod, invocationLogMessage.Arguments);
            throw;
        }
        finally
        {
            loggable.Logger.DebugFormat("Method {0} returned with value {1} and took exactly {2} to run.", invocationLogMessage.InvokedMethod, invocation.ReturnValue, stopwatch.Elapsed);
        }
    }

    #endregion IInterceptor Members
}

I want to write some unit tests for an interceptor that intercepts the Loggable base class (which implements ILoggable).
The Loggable base class has no methods to call and it is used only to be initialized by the logging facility.
To my understanding I should:

  1. Mock an ILoggable and an ILogger
  2. Initialize the logging facility
  3. Register my interceptor on it
  4. Invoke some method of the mocked ILoggable

The problem is that my ILoggable interface has no methods to call and thus nothing will be intercepted.
What is the right way to act here?
Should I mock ILoggable manually and add a stub method to call?
Also, should I be mocking the container as well?

I am using Moq and NUnit.
EDIT:
Here's my interceptor implementation for reference:

public class LoggingWithDebugInterceptor : IInterceptor
{
    #region IInterceptor Members

    public void Intercept(IInvocation invocation)
    {
        var invocationLogMessage = new InvocationLogMessage(invocation);

        ILoggable loggable = invocation.InvocationTarget as ILoggable;

        if (loggable == null)
            throw new InterceptionFailureException(invocation, string.Format("Class {0} does not implement ILoggable.", invocationLogMessage.InvocationSource));

        loggable.Logger.DebugFormat("Method {0} called with arguments {1}", invocationLogMessage.InvokedMethod, invocationLogMessage.Arguments);

        Stopwatch stopwatch = new Stopwatch();
        try
        {
            stopwatch.Start();
            invocation.Proceed();
            stopwatch.Stop();
        }
        catch (Exception e)
        {
            loggable.Logger.ErrorFormat(e, "An exception occured in {0} while calling method {1} with arguments {2}", invocationLogMessage.InvocationSource, invocationLogMessage.InvokedMethod, invocationLogMessage.Arguments);
            throw;
        }
        finally
        {
            loggable.Logger.DebugFormat("Method {0} returned with value {1} and took exactly {2} to run.", invocationLogMessage.InvokedMethod, invocation.ReturnValue, stopwatch.Elapsed);
        }
    }

    #endregion IInterceptor Members
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

沉鱼一梦 2024-11-10 18:05:33

如果只是拦截器使用类上的 Logger 属性,那为什么还要在那里呢?你也可以把它放在拦截器上。 (就像 Ayende 在他的帖子中解释的那样)。

除此之外 - 拦截器只是一个与接口交互的类 - 一切都高度可测试。

If it's just the interceptor that uses the Logger Property on your class than why have in there at all? You might just as well have it on the interceptor. (like Ayende explained in his post here).

Other than that - interceptor is just a class which interacts with an interface - everything highly testable.

以为你会在 2024-11-10 18:05:33

我同意 Krzysztof 的观点,如果您希望通过 AOP 添加日志记录,那么有关日志记录的责任和实现细节应该对调用者透明。因此,它是拦截器可以拥有的东西。我将尝试概述如何测试这一点。

如果我正确地理解了问题,您的 ILoggable 实际上只是一个注释类的命名容器,以便拦截器可以确定是否应该执行日志记录。它公开包含记录器的属性。 (这样做的缺点是该类仍然需要配置 Logger。)

public interface ILoggable
{
     ILogger { get; set; }
}

测试拦截器应该是一个直接的过程。我看到您提出的唯一挑战是如何手动构造IIn Vocation 输入参数,使其类似于运行时数据。我建议您使用经典的基于状态的验证来测试它,而不是尝试通过模拟等来重现这一点:创建一个使用您的拦截器的代理并验证您的日志是否反映了您的期望。

这可能看起来需要更多工作,但它提供了一个非常好的示例,说明拦截器如何独立于代码库的其他部分工作。您团队中的其他开发人员可以从中受益,因为他们可以参考此示例作为学习工具。

public class TypeThatSupportsLogging : ILoggable
{
     public ILogger { get; set; }

     public virtual void MethodToIntercept()
     {
     }

     public void MethodWithoutLogging()
     {
     }
}

public class TestLogger : ILogger
{
     private StringBuilder _output;

     public TestLogger()
     {
        _output = new StringBuilder();
     }

     public void DebugFormat(string message, params object[] args)
     {
        _output.AppendFormat(message, args);
     }

     public string Output
     {
        get { return _output.ToString(); }
     }
}

[TestFixture]
public class LoggingWithDebugInterceptorTests
{
     protected TypeThatSupportsLogging Input;
     protected LoggingWithDebugInterceptor Subject;
     protected ILogger Log;         

     [Setup]
     public void Setup()
     {
         // create your interceptor
         Subject = new LoggingWithDebugInterceptor();

         // create your proxy
         var generator = new Castle.DynamicProxy.ProxyGenerator();
         Input = generator.CreateClassProxy<TypeThatSupportLogging>( Subject );

         // setup the logger
         Log = new TestLogger();
         Input.Logger = Log;
     }

     [Test]
     public void DemonstrateThatTheInterceptorLogsInformationAboutVirtualMethods()
     {
          // act
          Input.MethodToIntercept();

          // assert
          StringAssert.Contains("MethodToIntercept", Log.Output);
     }

     [Test]
     public void DemonstrateNonVirtualMethodsAreNotLogged()
     {
          // act
          Input.MethodWithoutLogging();

          // assert
          Assert.AreEqual(String.Empty, Log.Output);
     }
}

I agree with Krzysztof, if you're looking to add Logging through AOP, the responsibility and implementation details about logging should be transparent to the caller. Thus it's something that the Interceptor can own. I'll try to outline how I would test this.

If I follow the question correctly, your ILoggable is really just a naming container to annotate the class so that the interceptor can determine if it should perform logging. It exposes a property that contains the Logger. (The downside to this is that the class still needs to configure the Logger.)

public interface ILoggable
{
     ILogger { get; set; }
}

Testing the interceptor should be a straight-forward process. The only challenge I see that you've presented is how to manually construct the IInvocation input parameter so that it resembles runtime data. Rather than trying to reproduce this through mocks, etc, I would suggest you test it using classic State-based verification: create a proxy that uses your interceptor and verify that your log reflects what you expect.

This might seem like a bit more work, but it provides a really good example of how the interceptor works independently from other parts of your code-base. Other developers on your team benefit from this as they can reference this example as a learning tool.

public class TypeThatSupportsLogging : ILoggable
{
     public ILogger { get; set; }

     public virtual void MethodToIntercept()
     {
     }

     public void MethodWithoutLogging()
     {
     }
}

public class TestLogger : ILogger
{
     private StringBuilder _output;

     public TestLogger()
     {
        _output = new StringBuilder();
     }

     public void DebugFormat(string message, params object[] args)
     {
        _output.AppendFormat(message, args);
     }

     public string Output
     {
        get { return _output.ToString(); }
     }
}

[TestFixture]
public class LoggingWithDebugInterceptorTests
{
     protected TypeThatSupportsLogging Input;
     protected LoggingWithDebugInterceptor Subject;
     protected ILogger Log;         

     [Setup]
     public void Setup()
     {
         // create your interceptor
         Subject = new LoggingWithDebugInterceptor();

         // create your proxy
         var generator = new Castle.DynamicProxy.ProxyGenerator();
         Input = generator.CreateClassProxy<TypeThatSupportLogging>( Subject );

         // setup the logger
         Log = new TestLogger();
         Input.Logger = Log;
     }

     [Test]
     public void DemonstrateThatTheInterceptorLogsInformationAboutVirtualMethods()
     {
          // act
          Input.MethodToIntercept();

          // assert
          StringAssert.Contains("MethodToIntercept", Log.Output);
     }

     [Test]
     public void DemonstrateNonVirtualMethodsAreNotLogged()
     {
          // act
          Input.MethodWithoutLogging();

          // assert
          Assert.AreEqual(String.Empty, Log.Output);
     }
}
故事灯 2024-11-10 18:05:33

没有方法吗?你在测试什么?

就个人而言,这听起来太过分了。我意识到 TDD 和代码覆盖率是教条,但如果您模拟一个没有方法的接口,并证明模拟框架按照您的指示执行操作,那么您真正证明了什么?

这里还有另一个误导:日志记录是面向方面编程的“hello world”。你为什么不记录拦截器/方面?如果您这样做,那么您的所有类就没有理由实现 ILoggable ;您可以用声明性的日志记录功能来装饰它们。我认为这是一种侵入性较小的设计,并且更好地利用了拦截器。

No methods? What are you testing?

Personally, this sounds like it goes too far. I realize that TDD and code coverage is dogma, but if you mock an interface with no methods and prove that the mocking framework does what you instructed it to do, what have you really proven?

There's another misdirection going on here: logging is the "hello world" of aspect oriented programming. Why aren't you doing logging in an interceptor/aspect? If you did it that way, there'd be no reason for all your classes to implement ILoggable; you could decorate them with logging capability declaratively. I think it's a less invasive design and a better use of interceptors.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文