2013-05-28 6 views
8

Meine Klasse hat diese Zeile:C# Statisch Readonly log4net Logger, wie kann man den Logger im Unit Test ändern?

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

Als ich Unit-Test gehen, ich moq Logger in diese Schnittstelle nicht spritzen kann, damit ich log Anrufe zählen konnte.

Gibt es eine Möglichkeit, dies zu tun? Log4net empfiehlt statische Schreibmuster für Logger. Was ist der beste Weg, damit umzugehen?

+0

Log4net unterstützt benutzerdefinierte Appender, so dass Sie alle Protokollierungsaufrufe auf diese Weise kennen sollten (ähnlich den meisten Logging-Frameworks - einschließlich Standard .Net one mit 'Trace') –

Antwort

9

Während log4net dieses Muster empfiehlt, verhindert nichts, dass Sie den Logger außerhalb der Klasse instanziieren und injizieren. Die meisten IoCs können so konfiguriert werden, dass sie ein und dieselbe Instanz injizieren. Auf diese Weise können Sie für Ihre Unit-Tests einen Mock injizieren.

würde ich einen Wrapper um LogManager.GetLogger empfehlen, die je nach Art immer ein und denselben Loggers Instanz zurückgibt:

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

Dies ist eine interessante Idee, ich vermute, es gibt keine echte Alternative, wenn readonly static verwendet wird. Was sind die Konsequenzen des Entfernens von Readonly oder Static? – AberAber

+0

readonly wird nicht entfernt, aber selbst wenn dies der Fall ist, gibt es keine Konsequenzen. Für die Statik gibt es auch keine Konsequenzen, soweit ein Singleton-Logger verwendet wird, und es wird auf der obigen Implementierung basieren. Sie werden bei jeder ClassWithLogger-Erstellung einen vernachlässigbaren Leistungseinbruch für die Fabrikarbeit bekommen, aber es ist sehr, sehr klein. Solange Sie also keinen Gerätetreiber schreiben oder gazzilion-Instanzen von ClassWithLogger erstellen, gibt es keinen wirklichen Nachteil. Der teure Teil ist die eigentliche Logger-Erstellung und wird vom Singleton gemildert. –

0

Sie MemoryAppender stattdessen verspottet Logger verwenden können. Mit diesem Ansatz können Sie log4net so konfigurieren, dass die Protokollereignisse im Speicher gesammelt und durch GetEvents() überprüft werden.

fand ich die diese nützliche Beispiele:

Hier ist der erste:

[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"); 
} 

Und die detaillierteren:

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(); 
    } 
}