0

プロジェクトで DI コンテナーとして Autofac を使用しています。今、Moqを使用して単体テストを作成し始めました。ビジネスクラスのコードはすでに書かれているので、ビジネスクラスの大幅な変更は避けたいと思います。System.IO.XXX クラス (FileSystemWatcher、Directory、File、StreamReader など) をモックする際に問題に直面しています。ほとんどの場合、静的クラスであるか、インターフェイスがないためです。

// これは、私のビジネス クラスの 1 つがどのように見えるかです

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;
    }
}
}

ここで、 Autofac を使用していくつかのインターフェイス ( IStreamReader など) を介してそのインスタンスを解決できるように、 StreamReader() を使用たいと思います。そのため、SpanFileReader()の単体テストを作成しているときに、 IStreamReader のMoq インスタンスをコンテナーに登録し、実際のインスタンスの代わりに使用できます。SUT ( SpanFileReaderインスタンス)のテスト時に呼び出される独自の moq 実装を提供できるように、 File()クラスでも同様のことを行いたいと考えています。誰かがこれらのシナリオに対処するための適切な方法を提案できますか.

4

2 に答える 2

1

あなたは2つの問題に言及します:

モックする方法は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コンテナ)を使用して、コードを置き換えます。テストするときは、Mock<IStreamReader>()代わりに次を返します。

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

モックする方法はFile

上記と同じように、代わりにコンストラクター/ラップされたオブジェクトなしIFileでクラスを作成します。FileWrapper

このラッパーのインスタンスをインスタンス化してクラス全体で使用し、通常使用する場所でこのインスタンスを使用します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?)。

もう1つのアイデアは、将来使用するためにさまざまなラップされた.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 に答える