2013-05-28 10 views
8

Moja klasa ma ten wiersz:C# Statyczny Rejestrator log4net tylko do odczytu, w jaki sposób można zmienić rejestrator w jednostce testowej?

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

Kiedy idę do testów jednostkowych, nie mogę wstrzyknąć rejestratora Min do tego interfejsu, więc mogłem liczyć połączenia zalogować.

Czy istnieje sposób, aby to zrobić? Log4net zaleca statyczny wzorzec tylko do odczytu dla rejestratorów. Jaki jest najlepszy sposób, aby sobie z tym poradzić?

+0

log4net obsługuje niestandardowych Appenders więc powinieneś wiedzieć o wszystkich rejestrowania rozmów w ten sposób (podobnie jak w przypadku większości rejestrowania ram - w tym jeden z domyślnie .Net 'trace') –

Odpowiedz

9

Podczas gdy log4net zaleca ten wzór, nic nie stoi na przeszkodzie, aby utworzyć rejestrator poza klasą i wstrzyknąć go. Większość IoC można skonfigurować do wstrzykiwania jednej i tej samej instancji. W ten sposób do testów jednostkowych można wstrzyknąć próbę.

Polecam otoki wokół LogManager.GetLogger, która zwraca zawsze jeden i ten sam przypadek rejestratora za typu:

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

Jest to ciekawy pomysł, podejrzewam, że nie ma rzeczywistego alternatywa w przypadku używania tylko do odczytu statycznego. Jakie są konsekwencje usunięcia tylko do odczytu lub statyczne? – AberAber

+0

readonly nie jest usuwany, ale nawet jeśli jest, nie ma żadnych konsekwencji. Dla statycznego również nie ma znaczenia, o ile używany jest rejestrator singletonowy, i będzie oparty na powyższej implementacji. Otrzymasz znikome uderzenie wydajności dla pracy fabrycznej przy każdym tworzeniu ClassWithLogger, ale jest bardzo małe. Więc jeśli nie piszesz sterownika urządzenia lub tworzysz instancje gazzilion ClassWithLogger, nie ma prawdziwej wady. Kosztowną częścią jest faktyczne tworzenie loggerów i jest łagodzone przez singleton. –

0

Można użyć MemoryAppender zamiast się wyśmiewali rejestratora. Dzięki takiemu podejściu można skonfigurować protokół log4net w taki sposób, aby gromadził zdarzenia z pamięci w pamięci i pobierać je w celu zweryfikowania przez GetEvents().

znalazłem tych użytecznych przykładów:

Oto pierwszy:

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

A im bardziej szczegółowe:

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