简体   繁体   English

实施单元测试接口

[英]Unit Testing Interface on Implementation

I have an interface which is used in an MVC controller which gets some data. 我有一个接口,用于获取一些数据的MVC控制器。 To keep it simple the interface so far looks something like this: 为了简单起见,到目前为止界面看起来像这样:

public interface IDataProvider
{
    DataModel GetData();
}

I have appropriate unit tests for this interface where it is called in the action. 我对此接口进行了适当的单元测试,在操作中调用它。 However, in the real implementation this will call a web service which of course could throw an exception, therefore if it does I want to write a test to ensure that I log a message if an error occurs. 但是,在实际实现中,这将调用Web服务,当然可以抛出异常,因此如果确实如此,我想编写测试以确保在发生错误时记录消息。

To do this I have a logger interface which is actually an interface to NLog called ILogger. 为此,我有一个记录器接口,它实际上是一个名为ILogger的NLog接口。 I could do this: 我能做到这一点:

public interface IDataProvider
{
    DataModel GetData(ILogger logger);
}

This would allow me to run unit tests for the logger making it nice and simple. 这将允许我为记录器运行单元测试,使其变得美观和简单。 However, I don't think this is the right way of doing this because the logger is really unrelated to this method. 但是,我不认为这是正确的方法,因为记录器与此方法实际上无关。 Also, if I start adding other methods to this interface which I need logging for then I will have to include the logger in the parameter of all of those methods as well. 此外,如果我开始向我需要记录的接口添加其他方法,那么我将必须在所有这些方法的参数中包含记录器。

The best way I can think of right now is to include the logger in the constructor of my implementation which might look like this: 我现在能想到的最好的方法是在我的实现的构造函数中包含logger,它可能如下所示:

public class DataProvider : IDataProvider
{
    private readonly ILogger _logger;

    public DataProvider(ILogger logger)
    {
        _logger = logger;
    }

    public DataModel GetData()
    {
        // CODE GOES HERE
    }
}

However this means that I cannot test the logger in my unit tests. 但是这意味着我无法在单元测试中测试记录器。 What is the best way to achieve this so that I can keep the logger separate from the method and make it testable? 实现这一目标的最佳方法是什么,以便我可以将记录器与方法分开并使其可测试?

I'll appreciate any help, thanks. 我会感激任何帮助,谢谢。

EDIT: 编辑:

I realise I missed out unit testing code here is what I mean: 我意识到我错过了单元测试代码这里是我的意思:

At the moment I am ensuring that GetData is called in my action this way: 目前我确保以这种方式在我的动作中调用GetData:

var controller = new DataController(_dataProvider.Object);

controller.Index();

_dataProvider.Verify(dataProvider => dataProvider.GetData());

What I'd like to do is the same but for the logger but only if an exception is thrown like this: 我想做的是相同但对于记录器,但只有在抛出异常时才会这样:

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

Obviously logger would be given to data provider in the setup. 显然,logger将在设置中提供给数据提供者。 I hope that makes a bit more sense. 我希望这更有意义。

You can try using the factory pattern. 您可以尝试使用工厂模式。

What happens here is in your production code, you are getting the logger from a Factory . 这里发生的是你的生产代码,你从工厂获得记录器。 In this factory, it returns either your real logger, or a fake logger which is setup in your unit tests. 在这个工厂中,它会返回您的真实记录器,或者在您的单元测试中设置的记录器。 To your production code, it makes no difference what-so-ever. 对于您的生产代码,它没有任何区别。

In your Unit Tests, you are using a fake logger created using Moq . 在单元测试中,您使用的是使用Moq创建的假记录器。 This fake allows you to test that an interface method was called, in this case ILogger.Log() . 这个假允许你测试一个接口方法被调用,在这种情况下是ILogger.Log() This is done by using the .Verify method. 这是通过使用.Verify方法完成的。

Try something like this: 尝试这样的事情:

ILogger.cs ILogger.cs

public interface ILogger
{
    void Log(string message);
}

LoggerFactory.cs 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 DataProvider.cs

public void GetData()
{
    var logger = LoggerFactory.Logger;

    logger.Log("..."); // etc...
}

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

If you need to test that the logger is being called, I suggest using a test double called a "spy". 如果您需要测试是否正在调用记录器,我建议使用称为“间谍”的测试双。 This would not do any logging, but keep track of which methods (if any) were called. 这不会进行任何记录,但会跟踪调用哪些方法(如果有的话)。 Then you can verify that the logger is called in specific instances. 然后,您可以验证在特定实例中是否调用了记录器。

You could do this by using a mocking framework to create the double (or mock) for you. 您可以通过使用模拟框架为您创建双(或模拟)来实现此目的。 Or you could create the ILogger implementation yourself. 或者您可以自己创建ILogger实现。 For example: 例如:

class LoggerSpy : ILogger
{
    public string LogWasCalled;

    public void Log(string message)
    {
        LogWasCalled = true;;
    }
}

The following seems to have an example of mocking an ILogger using Moq: How to Mock ILogger / ILoggerService using Moq 下面似乎有一个使用Moq 模拟ILogger的例子: 如何使用Moq 模拟ILogger / ILoggerService

Use a mocking framework (eg Moq or RhinoMocks) to verify that the logger was called. 使用模拟框架(例如Moq或RhinoMocks)来验证是否已调用记录器。 Then the final code block you post, where the logger is passed in via the constructor, will work. 然后,您发布的最终代码块(通过构造函数传入记录器)将起作用。

Passing logger (or any other dependencies) in constructor is very standard practice and allows you to use dependency injection framework if needed. 在构造函数中传递记录器(或任何其他依赖项)是非常标准的做法,并允许您在需要时使用依赖注入框架。

I'm not sure why you see passing logger in constructor as limiting for unit test: you have 3 components that you can test separately 我不确定为什么你看到在构造函数中传递logger作为单元测试的限制:你有3个组件可以单独测试

  • controller (depends on data provide, mock this dependency to test), controller(取决于提供的数据,模拟此依赖关系来测试),
  • data provider (depends on logging and some other classes that let you call web service - mock all dependencies so you know when logging called and no need to call Web service) 数据提供程序(取决于日志记录和一些其他允许您调用Web服务的类 - 模拟所有依赖项,以便您知道何时调用日志记录并且无需调用Web服务)
  • logging - not sure what it depends on, but should be testable separately. 记录 - 不确定它依赖于什么,但应该单独测试。

Notes: 笔记:

  • use mocking framework (ie moq ) for your tests - you'll be able to provide any implementations of interfaces (including exceptions) very easily. 使用模拟框架(即moq )进行测试 - 您将能够非常轻松地提供任何接口实现(包括异常)。
  • see if dependency injection framework (ie Unity ) would work for you. 看看依赖注入框架(即Unity )是否适合你。 MVC4 is very well suited for it. MVC4非常适合它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM