我在项目中使用Autofac作为DI容器。现在我已经开始使用Moq创建单元测试了。由于已经编写了业务类的代码,因此我希望避免在Business类中进行重大更改。我在模拟System.IO.XXX类(如FileSystemWatcher,Directory,File,StreamReader等)时遇到问题。主要是因为它们是静态类或者没有任何接口。
//这是我的业务类之一
internal class SpanFileReader : ISpanFileReader
{
// Some private variables
private string _filePath;
private readonly ISpanLogger _spanLogger;
#region Public Properties
// Prorperties....
#endregion
#region Constructor
public SpanFileReader(string filePath)
{
_filePath = filePath;
_spanLogger = IocContainer.Instance.Container.Resolve<ISpanLogger>();
}
#endregion
#region Public Methods
public bool ReadSpanRecords(CancellationToken ct)
{
try
{
if (!VerifySpanFile())
return false;
_spanFileLines = new List<string>();
using (var streamReader = new StreamReader(_filePath))
{
while (!streamReader.EndOfStream)
{
// some logic
}
return true;
}
}
catch (OperationCanceledException operationCanceledException)
{
_spanLogger.UpdateLog("some message");
throw;
}
catch (Exception ex)
{
_spanLogger.UpdateLog("some message);
throw;
}
}
public void MoveFileToErrorFolder(string spanFileName)
{
var spanFilePath = AppConfiguration.SpanFolderPath + spanFileName;
var errorFilePath = AppConfiguration.SpanErrorFolderPath + spanFileName;
try
{
if (File.Exists(spanFilePath))
{
if (!File.Exists(errorFilePath))
{
_spanLogger.UpdateLog("some message");
File.Move(spanFilePath, errorFilePath);
_spanLogger.UpdateLog("some message");
}
else
{
File.Delete(spanFilePath);
_spanLogger.UpdateLog("some message");
}
}
else
{
_spanLogger.UpdateLog("some message");
}
}
catch (Exception ex)
{
_spanLogger.UpdateLog("some message");
throw ex;
}
}
}
现在我想以这样的方式使用 StreamReader(),我可以使用 Autofac <通过某个界面(例如 IStreamReader )解析其实例/ strong>即可。因此,在为 SpanFileReader()编写单元测试时,我可以在容器中注册 IStreamReader的 Moq实例,并使用它代替实际实例。 我也想用 File()类做类似的事情,这样我就可以提供自己的moq实现,当SUT( SpanFileReader instace)被调用时)经过测试。 有人可以建议一个正确的方法来解决这些情况。
答案 0 :(得分:1)
你提到了两个问题:
如何模仿StreamReader
?
IStreamReader
。StreamReader
界面
StreamReaderWrapper
。IStreamReader
StreamReader
。e.g。
interface IStreamReader
{
string ReadLine();
// etc...
}
public class StreamReaderWrapper : IStreamReader
{
private StreamReader _streamReader;
public StreamReaderWrapper(string path)
{
_streamReader = new StreamReader(path);
}
public string ReadLine()
{
return _streamReader.ReadLine();
}
}
现在使用Autofac(或任何工厂/ IoC容器)替换代码中的new StreamReader()
。测试时,请返回Mock<IStreamReader>()
:
using (var streamReader =
IocContainer.Instance.Container.Resolve<IStreamReaderWrapper>(_filePath))
{
// ...
}
如何模仿File
?
执行与上面相同的操作,而不是创建一个没有构造函数/包装对象的IFile
和FileWrapper
类。
实例化此包装器的实例以供整个类使用,并在您通常使用File
时使用此实例。
E.g。
interface IFile
{
bool Exists(string name);
// etc...
}
public class FileWrapper : IFile
{
public bool Exists(string name)
{
return File.Exists(name);
}
}
然后(来自你的例子):
public SpanFileReader(string filePath)
{
// ...
_fileWrapper = IocContainer.Instance.Container.Resolve<IFileWrapper>();
}
public void MoveFileToErrorFolder(string spanFileName)
{
// ...
if (_fileWrapper.Exists(spanFilePath))
{
// ...
}
}
我发现这是一个非常干净的模式,因为所有的构造函数/方法签名都被保留了。测试不会触及文件系统而且容易被嘲笑是一个巨大的优势,而且速度也快得多。
一个重要的提示是避免向包装器添加任何逻辑,否则它们也可能需要测试(所以你必须创建一个包装器包装器......)!
类似File
的类也可以通过实例访问,所以你可能想创建单独的接口来单独保存实例和静态方法包装器(IFileStatics
?)。
另一个想法是构建一个包含各种包装的.Net类的库以供将来使用,或者使用各种.NET技术自动创建包装器(例如Castle Dynamic Proxy)。
答案 1 :(得分:0)
查看System.IO.Abstractions https://github.com/tathamoddie/System.IO.Abstractions。我最近才发现这一点,但它对我来说效果很好。
这基本上是一个包含System.IO中类的包装器的库,因此您可以将构造函数注入到类中,并在测试中传递模拟。
例如:
// register in autofac
builder.RegisterType<FileWrapper>().As<FileBase>();
public class MyClass
{
private readonly FileBase _file;
public MyClass(FileBase file)
{
_file = file;
}
public void MyMethod()
{
_file.Exists("...");
}
}