2013-05-28 47 views
8

我班有這樣一行:C#靜態只讀log4net記錄器,在單元測試中更改記錄器的方法?

private static readonly ILog log = LogManager.GetLogger(typeof(Prim)); 

當我去單元測試,我不能注入MOQ記錄到這個接口,所以我可以算日誌調用。

有沒有辦法做到這一點? Log4net爲記錄器推薦靜態只讀模式。處理它的最好方法是什麼?

+0

Log4net支持自定義Appender,因此您應該能夠以這種方式瞭解所有日誌記錄調用(類似於大多數日誌記錄框架 - 包括帶有Trace的默認.Net一個) –

回答

9

當log4net推薦這種模式時,沒有什麼能夠阻止你在類之外實例化記錄器並注入它。大多數IoC可以配置爲注入一個相同的實例。這樣,對於你的單元測試,你可以注入一個模擬。

我會建議身邊LogManager.GetLogger的包裝,它總是返回一個和每個類型相同的記錄器實例:

namespace StackOverflowExample.Moq 
{ 
    public interface ILogCreator 
    { 
     ILog GetTypeLogger<T>() where T : class; 
    } 

    public class LogCreator : ILogCreator 
    { 
     private static readonly IDictionary<Type, ILog> loggers = new Dictionary<Type, ILog>(); 
     private static readonly object lockObject; 

     public ILog GetTypeLogger<T>() where T : class 
     { 
      var loggerType = typeof (T); 
      if (loggers.ContainsKey(loggerType)) 
      { 
       return loggers[typeof (T)]; 
      } 

      lock (lockObject) 
      { 
       if (loggers.ContainsKey(loggerType)) 
       { 
        return loggers[typeof(T)]; 
       } 
       var logger = LogManager.GetLogger(loggerType); 
       loggers[loggerType] = logger; 
       return logger; 
      } 
     } 
    } 

    public class ClassWithLogger 
    { 
     private readonly ILog logger; 
     public ClassWithLogger(ILogCreator logCreator) 
     { 
      logger = logCreator.GetTypeLogger<ClassWithLogger>(); 
     } 

     public void DoSomething() 
     { 
      logger.Debug("called"); 
     } 
    } 

    [TestFixture] 
    public class Log4Net 
    { 
     [Test] 
     public void DoSomething_should_write_in_debug_logger() 
     { 
      //arrange 
      var logger = new Mock<ILog>(); 
      var loggerCreator = Mock.Of<ILogCreator>(
       c => 
       c.GetTypeLogger<ClassWithLogger>() == logger.Object); 

      var sut = new ClassWithLogger(loggerCreator); 

      //act 
      sut.DoSomething(); 

      //assert 
      logger.Verify(l=>l.Debug("called"), Times.Once()); 
     } 
    } 
} 
+0

這是一個有趣的想法,我懷疑沒有真正的如果使用只讀靜態選項。什麼是消除只讀或靜態的後果? – AberAber

+0

只讀不會被刪除,但即使是這樣,也不會有任何後果。對於靜態,也不會有任何後果,只要使用單例記錄器,它將基於上述實現。在每個ClassWithLogger創建過程中,工廠工作的性能影響可以忽略不計,但非常小。所以,除非你正在編寫設備驅動程序,或者你正在創建ClassWithLogger的gazzilion實例,否則沒有真正的缺點。昂貴的部分是實際的記錄器創建,並且由單例緩解。 –

0

您可以使用MemoryAppender而不是嘲笑記錄。通過這種方法,您可以配置log4net來收集內存中的日誌事件,並通過GetEvents()來驗證它們。

我發現這些有用的例子:

以下是第一條:

[SetUp] 
public void SetUp() 
{ 
    this.memoryAppender = new MemoryAppender(); 
    BasicConfigurator.Configure(this.memoryAppender); 

    ... 
} 

[Test] 
public void TestSomething() 
{ 
    ... 

    // Assert 
    Assert.IsFalse(
     this.memoryAppender.GetEvents().Any(le => le.Level == Level.Error), 
     "Did not expect any error messages in the logs"); 
} 

而且更詳細:

public static class Log4NetTestHelper 
{ 
    public static string[] RecordLog(Action action) 
    { 
     if (!LogManager.GetRepository().Configured) 
      BasicConfigurator.Configure(); 
     var logMessages = new List<string>(); 
     var root = ((log4net.Repository.Hierarchy.Hierarchy)LogManager.GetRepository()).Root; 
     var attachable = root as IAppenderAttachable; 

     var appender = new MemoryAppender(); 
     if (attachable != null) 
      attachable.AddAppender(appender); 

     try 
     {   
      action(); 
     } 
     finally 
     { 
      var loggingEvents = appender.GetEvents(); 
      foreach (var loggingEvent in loggingEvents) 
      { 
       var stringWriter = new StringWriter(); 
       loggingEvent.WriteRenderedMessage(stringWriter); 
       logMessages.Add(string.Format("{0} - {1} | {2}", loggingEvent.Level.DisplayName, loggingEvent.LoggerName, stringWriter.ToString())); 
      } 
      if (attachable != null) 
       attachable.RemoveAppender(appender); 
     } 

     return logMessages.ToArray(); 
    } 
}