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() чтобы проверить.

Я нашел эти полезные примеры:

здесь первый:

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

    //......
}