0

我在项目中使用 Autofac 作为 DI 容器。现在我已经开始在其中使用 Moq 创建单元测试。由于业务类的代码已经编写好了,所以我想避免对业务类进行重大更改。我在模拟 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)解析它的实例。因此,在为SpanFileReader()编写单元测试时,我可以在容器中注册IStreamReader 的Moq 实例并使用它而不是实际实例。我也想对File()类做类似的事情,这样我就可以提供我自己的 moq 实现,在测试 SUT(SpanFileReader实例)时调用它。有人可以建议一个正确的方法来处理这些场景。

4

2 回答 2

1

你提到了两个问题:

如何嘲笑StreamReader

  1. IStreamReader通过从 中复制方法来创建您的界面StreamReader
  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();
    }
}

现在new StreamReader()使用 Autofac(或任何工厂/IoC 容器)替换您的代码。测试时,返回 aMock<IStreamReader>()代替:

using (var streamReader =
    IocContainer.Instance.Container.Resolve<IStreamReaderWrapper>(_filePath))
{
    // ...
}

如何嘲笑File

执行与上面相同的操作,而不是创建一个没有构造函数/包装对象的IFileandFileWrapper类。

实例化此包装器的一个实例以供整个类使用,并在您通常使用的任何地方使用此实例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 Dynamic Proxy)自动创建包装器。

于 2013-01-11T07:16:22.650 回答
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("...");
    }

 }
于 2016-03-18T11:42:40.087 回答