实现的单元测试接口

本文关键字:接口 单元测试 实现 | 更新日期: 2023-09-27 18:06:22

我有一个在MVC控制器中使用的接口,它得到一些数据。为了保持简单,到目前为止,界面看起来像这样:

public interface IDataProvider
{
    DataModel GetData();
}

对于在操作中调用该接口的地方,我有适当的单元测试。然而,在实际的实现中,这将调用一个web服务,这当然可能抛出一个异常,因此,如果它这样做,我想写一个测试,以确保在发生错误时记录一条消息。

要做到这一点,我有一个记录器接口,它实际上是一个接口到NLog称为ILogger。我可以这样做:

public interface IDataProvider
{
    DataModel GetData(ILogger 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>());

显然,记录器将在设置中提供给数据提供程序。

实现的单元测试接口

您可以尝试使用工厂模式。

这里发生的事情是在您的生产代码中,您从工厂获得记录器。在这个工厂中,它返回真实的记录器,或者返回在单元测试中设置的假的记录器。对于您的产品代码来说,这没有什么区别。

在单元测试中,您使用的是使用Moq创建的假日志记录器。这个fake允许您测试是否调用了接口方法,在本例中是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...
}

有了

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

使用模拟框架(例如Moq或RhinoMocks)来验证是否调用了日志记录器。然后,您发布的最后一个代码块(其中通过构造函数传递日志记录器)就可以工作了。

在构造函数中传递logger(或任何其他依赖项)是非常标准的做法,允许您在需要时使用依赖注入框架。

我不知道为什么你看到在构造函数中传递logger作为单元测试的限制:你有3个组件,你可以单独测试

  • 控制器(取决于提供的数据,模拟此依赖项进行测试),
  • 数据提供程序(取决于日志记录和其他一些允许您调用web服务的类-模拟所有依赖项,以便您知道何时调用日志记录而不需要调用web服务)
  • 日志-不确定它取决于什么,但应该是可测试的单独

指出:

  • 使用mock框架(即moq)进行测试-您将能够非常轻松地提供接口的任何实现(包括异常)。
  • 看看依赖注入框架(如Unity)是否适合你。MVC4非常适合它。

如果您需要测试日志记录器是否正在被调用,我建议使用称为"spy"的测试double。这不会做任何日志记录,但会跟踪调用了哪些方法(如果有的话)。然后,您可以验证日志记录器是否在特定实例中被调用。

您可以通过使用mock框架为您创建双精度(或mock)来实现这一点。或者您可以自己创建ILogger实现。例如:

class LoggerSpy : ILogger
{
    public string LogWasCalled;
    public void Log(string message)
    {
        LogWasCalled = true;;
    }
}

下面似乎有一个使用Moq模拟ILogger的例子:如何使用Moq模拟ILogger/ILoggerService