4

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

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

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

我正在使用起订量和 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
}
4

3 回答 3

6

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

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

于 2011-04-28T02:09:52.850 回答
4

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

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

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

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

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

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);
     }
}
于 2011-04-28T05:46:02.553 回答
0

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

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

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

于 2011-04-27T11:31:23.747 回答