使用Moq和Autofac进行单元测试
本文关键字:单元测试 Autofac Moq 使用 | 更新日期: 2023-09-27 18:03:46
我有以下记录器记录器类,我想知道最好的单元测试它。
一些观察:
- 我需要创建接口IFileWrapper,以打破对系统的依赖。IO依赖,并能够使用依赖注入(Autofac)
-
我能够对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; } } } }
//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/如果你想要更多的细节
使用构造函数注入。简而言之,而不是通过设置属性来提供服务(在本例中是文件包装器),让记录器有一个接受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) {...}