在使用autofacc时,用c#模拟System.IO.XXX类

本文关键字:System IO XXX 模拟 autofacc | 更新日期: 2023-09-27 17:52:13

我在一个项目中使用Autofac作为DI容器。现在我已经开始使用Moq创建单元测试了。由于业务类的代码已经写好了,所以我想避免在业务类中进行重大更改。我在模拟System.IO.XXX类(如FileSystemWatcher, Directory, File, StreamReader等)时面临问题。主要是因为它们要么是静态类,要么没有任何接口。

//这是我的一个business类的样子

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()以这样一种方式,我可以通过一些接口(说IStreamReader)使用Autofac来解决它的实例。因此,在编写单元测试SpanFileReader()时,我可以在容器中注册IStreamReader的 Moq实例,并使用它而不是实际实例。我想与File()类做类似的事情,这样我就可以提供我自己的moq实现,当SUT (SpanFileReader实例)被测试时调用。有人能建议一个正确的方法来处理这些情况吗?

在使用autofacc时,用c#模拟System.IO.XXX类

你提到两个问题:

如何模拟StreamReader

  1. 通过复制StreamReader的方法创建IStreamReader接口。
  2. 创建一个包装器类StreamReaderWrapper实现IStreamReader
  3. 在包装器的构造函数中创建一个StreamReader
  4. 对于每个方法,将调用转发给包装对象。

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

和上面一样,创建一个IFileFileWrapper类,没有构造函数/包装对象。

实例化这个包装器的实例供整个类使用,并在通常使用File的地方使用这个实例。

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动态代理)自动创建包装器。

查看System.IO.Abstractions https://github.com/tathamoddie/System.IO.Abstractions。我最近才发现这一点,但它对我来说非常有效。

这基本上是一个包含System中类的包装器的库。因此,您可以将它们构造函数注入到您的类中,并在测试中通过mock。

例如:

// 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("...");
    }
 }