使用Moq和Autofac进行单元测试

本文关键字:单元测试 Autofac Moq 使用 | 更新日期: 2023-09-27 18:03:46

我有以下记录器记录器类,我想知道最好的单元测试它。

一些观察:

  1. 我需要创建接口IFileWrapper,以打破对系统的依赖。IO依赖,并能够使用依赖注入(Autofac)
  2. 我能够对FileWrapper方法进行单元测试。通过使用MemoryString实现IFileWrapper写入elog,但如果我想测试方法内的预期行为,我将无法(例如:抛出异常,不正确的路径和文件名等)

    /// <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;
                }
            }
        }
    }
    
因此,我使用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));

到目前为止一切顺利。我感到不舒服的是这一行:logger。FileWrapper = mock.Object;我想保持FileWrapper属性私有。

欢迎提出任何建议。

我将发布代码http://csharplogger.codeplex.com/如果你想要更多的细节

使用Moq和Autofac进行单元测试

使用构造函数注入。简而言之,而不是通过设置属性来提供服务(在本例中是文件包装器),让记录器有一个接受IFileWrapper参数的公共构造函数。

public class Logger
{
    public Logger(IFileWrapper fileWrapper)
    {
        FileWrapper = fileWrapper;
    }
    public IFileWrapper FileWrapper { get; }
}
// in your test:
var logger = new Logger(mock.Object);
为了更彻底地回答关于使用单例文件包装器的问题,下面是应用程序(非测试)代码的代码示例:
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.");
    }
}

由于FileWrapperFactory维护文件包装器的静态实例,因此您永远不会拥有多个实例。但是,您可以创建多个这样的记录器,它们不必关心。如果您将来决定可以使用多个文件包装器,则记录器代码不必更改。

在真实的应用程序中,我建议你选择某种DI框架来为你处理所有这些簿记;大多数都对单例实例有很好的支持,基本上做了上面的FileWrapperFactory所做的(但通常以更复杂和健壮的方式)。FileWrapperFactory不是线程安全的,例如…)。

由于您的代码注释显示您的日志记录器是单例的,因此您需要一种除了构造函数注入之外的方法来设置依赖项。Mike Feathers在他的关于遗留代码的书中建议了一个用于此目的的函数,它的名称很恰当,类似于

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