测试日志记录的最佳方法?
Best way to test logging?
我的代码使用这样的 ILogger 记录:
public class Calculator
{
private readonly ILogger _logger;
public Calculator(ILogger<Calculator> logger)
{
_logger = logger;
}
public int Sum(int x, int y)
{
int sum = x + y;
_logger.LogInformation("The sum of {x} and {y} is {sum}.", x, y, sum);
return sum;
}
}
现在我想确定,当我调用 Sum(2,3) 时,LogInformation 包含数字 2、3 和 5。
如何在 Xunit(最小起订量)中捕获它?
如果是我,我会使用 Moq.Contrib.ExpressionBuilders.Logging(免责声明:我是作者)来构建表达式。它正是为此目的而设计的,并提供了一个简单易读的声明。
loggerMock.Verify(Log.With.LogLevel(LogLevel.Information).And.LogMessage("The sum of {x} and {y} is {sum}.").And.LoggedValue("x", 2).And.LoggedValue("y", 3).And.LoggedValue("sum", 5), Times.Once);
如果您想自己滚动,则需要对 Log
方法执行验证并匹配日志级别和包含 message/logged 值的只读列表。
日志级别很简单。要匹配 message/logged 值,您需要在 TState
参数(第 3 个参数)上添加一个匹配器,如下所示:
var loggerMock = new Mock<ILogger<Calculator>>();
var logger = loggerMock.Object;
var calculator = new Calculator(logger);
calculator.Sum(2, 3);
loggerMock.Verify(l => l.Log(
It.Is<LogLevel>(x => x.Equals(LogLevel.Information)),
It.IsAny<EventId>(),
It.Is<It.IsAnyType>((o, t) =>
((IReadOnlyList<KeyValuePair<string, object>>)o).Last().Value.ToString().Equals("The sum of {x} and {y} is {sum}.") &&
((IReadOnlyList<KeyValuePair<string, object>>)o).Any(y => y.Key.Equals("x") && y.Value.Equals(2)) &&
((IReadOnlyList<KeyValuePair<string, object>>)o).Any(y => y.Key.Equals("y") && y.Value.Equals(3)) &&
((IReadOnlyList<KeyValuePair<string, object>>)o).Any(y => y.Key.Equals("sum") && y.Value.Equals(5))
),
It.IsAny<Exception>(),
(Func<It.IsAnyType, Exception, string>)It.IsAny<object>()),
Times.Once);
在幕后,TState 是一个 FormattedLogValues
对象,它实现了 IReadOnlyList<KeyValuePair<string, object>>
。 FormattedLogValues
在 .NET Core 3.* 中是内部的,因此您无法访问它。最后一项是 {OriginalFormat}
,这是提供给记录器的日志消息。
我的代码使用这样的 ILogger 记录:
public class Calculator
{
private readonly ILogger _logger;
public Calculator(ILogger<Calculator> logger)
{
_logger = logger;
}
public int Sum(int x, int y)
{
int sum = x + y;
_logger.LogInformation("The sum of {x} and {y} is {sum}.", x, y, sum);
return sum;
}
}
现在我想确定,当我调用 Sum(2,3) 时,LogInformation 包含数字 2、3 和 5。 如何在 Xunit(最小起订量)中捕获它?
如果是我,我会使用 Moq.Contrib.ExpressionBuilders.Logging(免责声明:我是作者)来构建表达式。它正是为此目的而设计的,并提供了一个简单易读的声明。
loggerMock.Verify(Log.With.LogLevel(LogLevel.Information).And.LogMessage("The sum of {x} and {y} is {sum}.").And.LoggedValue("x", 2).And.LoggedValue("y", 3).And.LoggedValue("sum", 5), Times.Once);
如果您想自己滚动,则需要对 Log
方法执行验证并匹配日志级别和包含 message/logged 值的只读列表。
日志级别很简单。要匹配 message/logged 值,您需要在 TState
参数(第 3 个参数)上添加一个匹配器,如下所示:
var loggerMock = new Mock<ILogger<Calculator>>();
var logger = loggerMock.Object;
var calculator = new Calculator(logger);
calculator.Sum(2, 3);
loggerMock.Verify(l => l.Log(
It.Is<LogLevel>(x => x.Equals(LogLevel.Information)),
It.IsAny<EventId>(),
It.Is<It.IsAnyType>((o, t) =>
((IReadOnlyList<KeyValuePair<string, object>>)o).Last().Value.ToString().Equals("The sum of {x} and {y} is {sum}.") &&
((IReadOnlyList<KeyValuePair<string, object>>)o).Any(y => y.Key.Equals("x") && y.Value.Equals(2)) &&
((IReadOnlyList<KeyValuePair<string, object>>)o).Any(y => y.Key.Equals("y") && y.Value.Equals(3)) &&
((IReadOnlyList<KeyValuePair<string, object>>)o).Any(y => y.Key.Equals("sum") && y.Value.Equals(5))
),
It.IsAny<Exception>(),
(Func<It.IsAnyType, Exception, string>)It.IsAny<object>()),
Times.Once);
在幕后,TState 是一个 FormattedLogValues
对象,它实现了 IReadOnlyList<KeyValuePair<string, object>>
。 FormattedLogValues
在 .NET Core 3.* 中是内部的,因此您无法访问它。最后一项是 {OriginalFormat}
,这是提供给记录器的日志消息。