我班有這樣一行:C#靜態只讀log4net記錄器,在單元測試中更改記錄器的方法?
private static readonly ILog log = LogManager.GetLogger(typeof(Prim));
當我去單元測試,我不能注入MOQ記錄到這個接口,所以我可以算日誌調用。
有沒有辦法做到這一點? Log4net爲記錄器推薦靜態只讀模式。處理它的最好方法是什麼?
我班有這樣一行:C#靜態只讀log4net記錄器,在單元測試中更改記錄器的方法?
private static readonly ILog log = LogManager.GetLogger(typeof(Prim));
當我去單元測試,我不能注入MOQ記錄到這個接口,所以我可以算日誌調用。
有沒有辦法做到這一點? Log4net爲記錄器推薦靜態只讀模式。處理它的最好方法是什麼?
當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());
}
}
}
這是一個有趣的想法,我懷疑沒有真正的如果使用只讀靜態選項。什麼是消除只讀或靜態的後果? – AberAber
只讀不會被刪除,但即使是這樣,也不會有任何後果。對於靜態,也不會有任何後果,只要使用單例記錄器,它將基於上述實現。在每個ClassWithLogger創建過程中,工廠工作的性能影響可以忽略不計,但非常小。所以,除非你正在編寫設備驅動程序,或者你正在創建ClassWithLogger的gazzilion實例,否則沒有真正的缺點。昂貴的部分是實際的記錄器創建,並且由單例緩解。 –
您可以使用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();
}
}
Log4net支持自定義Appender,因此您應該能夠以這種方式瞭解所有日誌記錄調用(類似於大多數日誌記錄框架 - 包括帶有Trace的默認.Net一個) –