C# Static Readonly log4net logger, любой способ изменить регистратор в модульном тесте?
мой класс имеет эту строку:
private static readonly ILog log = LogManager.GetLogger(typeof(Prim));
когда я иду в модульный тест, я не могу ввести регистратор moq в этот интерфейс, чтобы я мог считать вызовы журнала.
есть ли способ сделать это? Такой как log4net рекомендует статический шаблон readonly для лесорубов. Как лучше с этим справиться?
3 ответов
хотя log4net рекомендует этот шаблон, ничто не мешает вам создавать экземпляр регистратора вне класса и вводить его. Большинство IoCs можно настроить для ввода одного и того же экземпляра. Таким образом, для ваших модульных тестов вы можете ввести макет.
Я бы рекомендовал обертку вокруг 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());
}
}
}
можно использовать MemoryAppender
вместо насмешливого лесоруба. С помощью этого подхода можно настроить log4net для сбора событий журнала в памяти и получения их с помощью GetEvents()
чтобы проверить.
Я нашел эти полезные примеры:
- https://gist.github.com/plaurin/3563082#file-trycache-cs
- http://jake.ginnivan.net/verifying-logged-messages-with-log4net/
здесь первый:
[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();
}
}
просто возьми readonly
прочь, регистратор будет работать так же, как обычный объект интерфейса.
см. следующий простой тестируемый пример:
using System;
using log4net;
namespace StackOverflowExample.Moq
{
public class ClassWithLogger
{
private static ILog _log;
//private static readonly ILog log = LogManager.GetLogger(typeof(Prim));
public ClassWithLogger(ILog log)
{
_log = log;
}
public void DoSomething(object para = null)
{
try
{
if(para != null)
{
_log.Debug("called");
}
else
{
throw new System.ArgumentException("Parameter cannot be null");
}
}
catch (Exception ex)
{
_log.Fatal("Exception raised!", ex);
}
}
}
}
Код Модульного Теста:
[TestMethod]
public void Test_DoSomething_logger()
{
//arrange
var mockLog = new Mock<log4net.ILog>();
var classWithLogger = new ClassWithLogger(mockLog.Object);
mockLog.Setup(m => m.Debug(It.IsAny<string>()));
mockLog.Setup(m => m.Fatal(It.IsAny<string>(), It.IsAny<Exception>()));
//act1
classWithLogger.DoSomething(new object());
//assert1
mockLog.Verify(l => l.Debug("called"), Times.Once());
//act2
classWithLogger.DoSomething();
//assert2
mockLog.Verify(x => x.Fatal("Exception raised!", It.IsAny<Exception>()));
}
и основной программы вызов:
public void MainProgramCall()
{
//......
ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
var classWithLogger = new ClassWithLogger(log);
classWithLogger.DoSomething(new object());
//......
classWithLogger.DoSomething();
//......
}