简体   繁体   English

使用Moq和Autofac进行单元测试

[英]Unit Testing using Moq and Autofac

I have the following logger logger class and I want to know the best to unit testing it. 我有以下记录器记录器类,我想知道最好对其进行单元测试。

Some observations: 一些观察:

  1. I needed to create the interface IFileWrapper in order to break dependency with System.IO dependency and been able to user dependency injection (Autofac) 我需要创建接口IFileWrapper以破坏与System.IO依赖关系的依赖性并能够用户依赖注入(Autofac)
  2. I was able to unit testing the method FileWrapper.WriteLog by implementing IFileWrapper using a MemoryString but if I wanted to test a expected behavior inside the method I won't be able (eg: throwing exceptions, incorrect path and filename, etc.) 我能够通过使用MemoryString实现IFileWrapper来对FileWrapper.WriteLog方法进行单元测试,但是如果我想测试方法中的预期行为,我将无法做到(例如:抛出异常,错误的路径和文件名等)

     /// <summary> /// Creates an instance of type <see cref="FileLogger"/> /// </summary> /// <remarks>Implements the Singleton Pattern</remarks> private FileLogger() { FileName = string.Format("\\\\{0: MMM dd, yy}.log", DateTime.Now); Path = Environment.CurrentDirectory; FileWrapper = ContainerBuilderFactory.Container.Resolve<IFileWrapper>(); } /// <summary> /// Log the <paramref name="Message"/> in the <paramref name="Path"/> specified. /// The <paramref name="UserName"/>, <paramref name="Host"/> must be supplied /// </summary> /// <example> /// <code> /// var handler = new LoggerHandlerFactory(); /// var logger = handler.GetHandler<FileLogger>(); /// logger.Log("Hello CSharpLogger"); /// </code> /// </example> /// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentException"></exception> /// <exception cref="NotSupportedException"></exception> /// <exception cref="FileNotFoundException"></exception> /// <exception cref="IOException"></exception> /// <exception cref="SecurityException"></exception> /// <exception cref="DirectoryNotFoundException"></exception> /// <exception cref="UnauthorizedAccessException"></exception> /// <exception cref="PathTooLongException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> /// <exception cref="FormatException"></exception> public void Log(string message, LogLevel level = LogLevel.INFO) { lock (_current) { var configLevel = CSharpLoggerConfiguration.Configuration.GetLogLevel(); if (configLevel != LogLevel.OFF & level != LogLevel.OFF && configLevel >= level) { try { FileWrapper.WriteLog(string.Concat(Path, FileName), message, level); } catch (CSharpLoggerException) { throw; } } } } 

So, I created the following UnitTesting using Moq: 所以,我使用Moq创建了以下UnitTesting:

 //arrange
        CSharpLoggerConfiguration.Configuration.SetLogLevel(LogLevel.DEBUG);

        var mock = new Mock<IFileWrapper>();
        mock.Setup(x => x.WriteLog(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<LogLevel>()));

        logger.FileWrapper = mock.Object;

        //act
        logger.Log("Hello CSharpLogger", LogLevel.DEBUG);
        logger.Log("Hello CSharpLogger", LogLevel.WARN);

        //assert 
        mock.Verify(x => x.WriteLog(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<LogLevel>()), Times.Exactly(2));

So far so good. 到现在为止还挺好。 What I'm not confortable is with this line: logger.FileWrapper = mock.Object; 我不舒服的是这一行:logger.FileWrapper = mock.Object; I would like to keep FileWrapper propety private. 我想保持FileWrapper propety私有。

Any advise is welcome. 任何建议都是受欢迎的。

I'll be publishing the code http://csharplogger.codeplex.com/ in case you want more details. 如果您需要更多详细信息,我将发布代码http://csharplogger.codeplex.com/

Use constructor injection . 使用构造函数注入 In short; 简而言之; instead of providing the service (in this case the file wrapper) by setting a property, make the logger have a public constructor which takes an IFileWrapper argument. 而不是通过设置属性来提供服务(在本例中为文件包装器),使记录器具有一个公共构造函数,该构造函数接受IFileWrapper参数。

public class Logger
{
    public Logger(IFileWrapper fileWrapper)
    {
        FileWrapper = fileWrapper;
    }

    public IFileWrapper FileWrapper { get; }
}

// in your test:
var logger = new Logger(mock.Object);

To answer the question about having a singleton file wrapper more thoroughly, here's a code sample for the application (non-test) code: 要回答有关单个文件包装器更彻底的问题,这里是应用程序(非测试)代码的代码示例:

public static class FileWrapperFactory
{
    private static IFileWrapper _fileWrapper;

    public static IFileWrapper GetInstance()
    {
        return _fileWrapper ?? (_fileWrapper = CreateInstance());
    }

    private static IFileWrapper CreateInstance()
    {
        // do all the necessary setup here
        return new FileWrapper();
    }
}


public class StuffDoer
{
    public void DoStuff()
    {
        var logger = new FileLogger(FileWrapperFactory.GetInstance());

        logger.WriteLog("Starting to do stuff...");

        // do stuff

        logger.WriteLog("Stuff was done.");
    }
}

Since the FileWrapperFactory maintains a static instance of the file wrapper, you'll never have more than one. 由于FileWrapperFactory维护文件包装器的静态实例,因此您将永远不会有多个。 However, you can create multiple loggers like that, and they don't have to care. 但是,您可以创建这样的多个记录器,并且他们不必关心。 If you, in the future, decide that it's OK to have many file wrappers, the logger code doesn't have to change. 如果您将来决定使用多个文件包装器,则无需更改记录器代码。

In a real-world application, I'd advice you to choose some kind of DI framework to handle all this book-keeping for you; 在实际应用程序中,我建议您选择某种DI框架来处理所有这些书籍; most have excellent support for singleton instances, doing essentially what the FileWrapperFactory above does (but usually in a more sophisticated and robust way. FileWrapperFactory isnt' thread-safe, for example...). 大多数人都对单例实例有很好的支持,基本上FileWrapperFactory上面的FileWrapperFactory一样(但通常是以更复杂和健壮的方式。 FileWrapperFactory不是'线程安全的,例如......)。

Since your code comments show that your logger is a singleton, you need a way other than constructor injection for setting the dependency. 由于您的代码注释显示您的记录器是单例,因此您需要一种除构造函数注入之外的方法来设置依赖项。 In his book on Legacy Code, Mike Feathers suggests a function for such purposes, which is adequately named, something like 在他的关于遗产代码的书中,Mike Feathers为这样的目的提出了一个功能,这个功能已被充分命名,类似于

public void SetInstanceForTesting(IFileWrapper fileWrapper) {...}

Now this function won't hopefully be used for different purposes... 现在这个功能不会用于不同的目的......

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

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