我有以下记录器记录器类,我想知道如何对其进行单元测试。
一些观察:
- 我需要创建接口(interface) IFileWrapper 以打破对 System.IO 的依赖,并能够进行用户依赖注入(inject) (Autofac)
我能够通过使用 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; } } } }
因此,我使用 Moq 创建了以下单元测试:
//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/如果您需要更多详细信息。
最佳答案
使用constructor injection .简而言之;不是通过设置属性来提供服务(在本例中是文件包装器),而是使记录器具有一个公共(public)构造函数,该构造函数采用 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
不是线程安全的。 ..).
关于c# - 使用 Moq 和 Autofac 进行单元测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39114925/