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

时间:2011-04-27 11:22:54

标签: unit-testing mocking castle-dynamicproxy

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

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

    问题是我的 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
    }
    

3 个答案:

答案 0 :(得分:6)

如果它只是在您的类上使用Logger属性的拦截器而不是为什么在那里?你也可以在拦截器上拥有它。 (就像Ayende在他的post here中解释的那样)。

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

答案 1 :(得分:4)

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

如果我正确地遵循了这个问题,你的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);
     }
}

答案 2 :(得分:0)

没办法?你在测试什么?

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

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

相关问题