我有一个接口,用于获取一些数据的MVC控制器。为了简单起见,到目前为止界面看起来像这样:
public interface IDataProvider
{
DataModel GetData();
}
我为此界面进行了适当的单元测试,在操作中调用它。但是,在实际实现中,这将调用Web服务,当然可以抛出异常,因此如果确实如此,我想编写测试以确保在发生错误时记录消息。
为此,我有一个记录器接口,它实际上是一个名为ILogger的NLog接口。我能做到这一点:
public interface IDataProvider
{
DataModel GetData(ILogger logger);
}
这将允许我为记录器运行单元测试,使其变得简单明了。但是,我不认为这是正确的方法,因为记录器与此方法实际上无关。另外,如果我开始向我需要记录的接口添加其他方法,那么我将必须在所有这些方法的参数中包含记录器。
我现在能想到的最好的方法是在我的实现的构造函数中包含logger,它可能如下所示:
public class DataProvider : IDataProvider
{
private readonly ILogger _logger;
public DataProvider(ILogger logger)
{
_logger = logger;
}
public DataModel GetData()
{
// CODE GOES HERE
}
}
然而,这意味着我无法在单元测试中测试记录器。实现这一目标的最佳方法是什么,以便我可以将记录器与方法分开并使其可测试?
感谢您的帮助,谢谢。
编辑:
我意识到我错过了单元测试代码,这就是我的意思:
目前我确保以这种方式调用GetData:
var controller = new DataController(_dataProvider.Object);
controller.Index();
_dataProvider.Verify(dataProvider => dataProvider.GetData());
我想做的事情是相同的,但对于记录器而言,只有在抛出异常时才会这样:
_dataProvider.Setup(dataProvider => dataProvider.GetData()).Throws<WebException>();
var controller = new DataController(_dataProvider.Object);
controller.Index();
_logger.Verify(logger => logger.ErrorException(It.IsAny<string>(), It.IsAny<Exception>());
显然,logger将在设置中提供给数据提供者。我希望这更有意义。
答案 0 :(得分:6)
您可以尝试使用工厂模式。
这里发生的是您的生产代码,您从工厂获取记录器。在这个工厂中,它会返回您的真实记录器,或者在单元测试中设置的假记录器。对于您的生产代码,它没有任何区别。
在您的单元测试中,您使用的是使用Moq创建的假记录器。这个假允许您测试调用接口方法,在本例中为ILogger.Log()
。这是通过使用.Verify
方法完成的。
尝试这样的事情:
<强> ILogger.cs 强>
public interface ILogger
{
void Log(string message);
}
<强> LoggerFactory.cs 强>
public static class LoggerFactory
{
public static ILogger Logger
{
get
{
return LoggerFactory._logger == null ? new Logger() : LoggerFactory._logger;
}
set
{
LoggerFactory._logger = value;
}
}
private static ILogger _logger = null;
}
<强> DataProvider.cs 强>
public void GetData()
{
var logger = LoggerFactory.Logger;
logger.Log("..."); // etc...
}
<强> UnitTest.cs 强>
private void Mock<ILogger> _mockLogger = null;
public void Load()
{
this._mockLogger = new Mock<ILogger>();
LoggerFactory.Logger = _mockLogger.Object;
}
public void UnitTest()
{
// test as required
this._mockLogger.Verify(m => m.Log(It.IsAny<string>()));
}
答案 1 :(得分:1)
如果您需要测试是否正在调用记录器,我建议使用称为“间谍”的测试双。这不会进行任何记录,但会跟踪调用哪些方法(如果有的话)。然后,您可以验证在特定实例中是否调用了记录器。
您可以通过使用模拟框架为您创建双(或模拟)来实现此目的。或者您可以自己创建ILogger
实现。例如:
class LoggerSpy : ILogger
{
public string LogWasCalled;
public void Log(string message)
{
LogWasCalled = true;;
}
}
以下似乎有一个使用Moq模拟ILogger的示例:How to Mock ILogger / ILoggerService using Moq
答案 2 :(得分:1)
使用模拟框架(例如Moq或RhinoMocks)来验证是否已调用记录器。然后,您发布的最终代码块(通过构造函数传入记录器)将起作用。
答案 3 :(得分:1)
在构造函数中传递logger(或任何其他依赖项)是非常标准的做法,并允许您在需要时使用依赖注入框架。
我不确定为什么你看到在构造函数中传递logger作为单元测试的限制:你有3个组件可以单独测试
注意: