web-dev-qa-db-ja.com

メソッドをモックして例外(moq)をスローしますが、それ以外はモックされたオブジェクトのように動作しますか?

Transferクラスがあり、次のように簡略化されています。

public class Transfer
{
    public virtual IFileConnection source { get; set; }
    public virtual IFileConnection destination { get; set; }

    public virtual void GetFile(IFileConnection connection, 
        string remoteFilename, string localFilename)
    {
        connection.Get(remoteFilename, localFilename);
    }

    public virtual void PutFile(IFileConnection connection, 
        string localFilename, string remoteFilename)
    {
        connection.Get(remoteFilename, localFilename);
    }

    public virtual void TransferFiles(string sourceName, string destName)
    {
        source = internalConfig.GetFileConnection("source");
        destination = internalConfig.GetFileConnection("destination");
        var tempName = Path.GetTempFileName();
        GetFile(source, sourceName, tempName);
        PutFile(destination, tempName, destName);
    }
}

IFileConnectionインターフェイスの簡易バージョンは次のようになります。

public interface IFileConnection
{
    void Get(string remoteFileName, string localFileName);
    void Put(string localFileName, string remoteFileName);
}

実際のクラスは、IFileConnection具象クラスがリモートとの接続を失ったときにスローされるSystem.IO.IOExceptionを処理することになっています。

Moqを使用してTransferクラスを作成し、Transferメソッドが呼び出される場合を除き、すべてのプロパティとメソッドで具体的なGetFileクラスとして使用したいSystem.IO.IOExceptionをスローし、Transferクラスがそれを適切に処理するようにします。

仕事に適したツールを使用していますか?私はこれについて正しい方法で行っていますか?そして、NUnitの単体テストのセットアップをどのように書きますか?

47
Jeremy Holovacs

これは私がやろうとしていたことをどうやってやったかです:

[Test]
public void TransferHandlesDisconnect()
{
    // ... set up config here
    var methodTester = new Mock<Transfer>(configInfo);
    methodTester.CallBase = true;
    methodTester
        .Setup(m => 
            m.GetFile(
                It.IsAny<IFileConnection>(), 
                It.IsAny<string>(), 
                It.IsAny<string>()
            ))
        .Throws<System.IO.IOException>();

    methodTester.Object.TransferFiles("foo1", "foo2");
    Assert.IsTrue(methodTester.Object.Status == TransferStatus.TransferInterrupted);
}

この方法に問題がある場合、私は知りたいです。他の答えは、私がこれを間違っていることを示唆していますが、これはまさに私がやろうとしていたことでした。

9
Jeremy Holovacs

FileConnectionをモックする方法は次のとおりです。

Mock<IFileConnection> fileConnection = new Mock<IFileConnection>(
                                                           MockBehavior.Strict);
fileConnection.Setup(item => item.Get(It.IsAny<string>,It.IsAny<string>))
              .Throws(new IOException());

次に、Transferクラスをインスタンス化し、メソッド呼び出しでモックを使用します

Transfer transfer = new Transfer();
transfer.GetFile(fileConnection.Object, someRemoteFilename, someLocalFileName);

更新:

まず、テストするクラス(この場合はTransferクラス)ではなく、依存関係のみをモックする必要があります。コンストラクターでこれらの依存関係を記述すると、クラスが機能するために必要なサービスを簡単に確認できます。また、単体テストを作成するときに、それらを偽物に置き換えることができます。現時点では、これらのプロパティを偽物に置き換えることは不可能です。

別の依存関係を使用してこれらのプロパティを設定しているため、次のように記述します。

public class Transfer
{
    public Transfer(IInternalConfig internalConfig)
    {
        source = internalConfig.GetFileConnection("source");
        destination = internalConfig.GetFileConnection("destination");
    }

    //you should consider making these private or protected fields
    public virtual IFileConnection source { get; set; }
    public virtual IFileConnection destination { get; set; }

    public virtual void GetFile(IFileConnection connection, 
        string remoteFilename, string localFilename)
    {
        connection.Get(remoteFilename, localFilename);
    }

    public virtual void PutFile(IFileConnection connection, 
        string localFilename, string remoteFilename)
    {
        connection.Get(remoteFilename, localFilename);
    }

    public virtual void TransferFiles(string sourceName, string destName)
    {
        var tempName = Path.GetTempFileName();
        GetFile(source, sourceName, tempName);
        PutFile(destination, tempName, destName);
    }
}

このようにして、internalConfigをモックし、必要なことを行うIFileConnectionモックを返すことができます。

65

私はこれがあなたが望むものだと思う、私はすでにこのコードをテストして動作します

使用されるツールは次のとおりです(これらのツールはすべてNugetパッケージとしてダウンロードできます)。

http://fluentassertions.codeplex.com/

http://autofixture.codeplex.com/

http://code.google.com/p/moq/

https://nuget.org/packages/AutoFixture.AutoMoq

_var fixture = new Fixture().Customize(new AutoMoqCustomization());
var myInterface = fixture.Freeze<Mock<IFileConnection>>();

var sut = fixture.CreateAnonymous<Transfer>();

myInterface.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<string>()))
        .Throws<System.IO.IOException>();

sut.Invoking(x => 
        x.TransferFiles(
            myInterface.Object, 
            It.IsAny<string>(), 
            It.IsAny<string>()
        ))
        .ShouldThrow<System.IO.IOException>();
_

編集済み:

説明させてください:

テストを作成するとき、テストする内容を正確に知る必要があります。これは「テスト対象(SUT)」と呼ばれます。私の理解が正しければ、この場合、SUTはTransferです。

したがって、これを念頭に置いて、SUTを偽装しないでください。SUTを置き換える場合、実際のコードを実際にテストすることはありません

SUTに外部依存関係がある(非常に一般的)場合、SUTをisolationでテストするためにそれらを置き換える必要があります。私が代替と言うとき、私はあなたのニーズに応じてモック、ダミー、モックなどを使用することを言っています

この場合、外部依存関係はIFileConnectionであるため、この依存関係のモックを作成して例外をスローするように構成し、SUTの実際のメソッドを呼び出して、メソッドが期待どおりに例外を処理することをアサートする必要があります

  • var fixture = new Fixture().Customize(new AutoMoqCustomization());:このリニーは、新しいFixtureオブジェクト(Autofixtureライブラリ)を初期化します。このオブジェクトは、コンストラクタパラメータを明示的に心配する必要なくSUTを作成するために使用されます。 Moqを使用する

  • var myInterface = fixture.Freeze<Mock<IFileConnection>>();:これは、IFileConnection依存関係をフリーズします。フリーズとは、Autofixtureが尋ねられたときに常にこの依存関係を使用することを意味します。簡単にするためにシングルトンのように。しかし、興味深い部分は、この依存関係のモックを作成していることです。これはすべてのMoqメソッドを使用できます。これは単純なMoqオブジェクトであるためです。

  • var sut = fixture.CreateAnonymous<Transfer>();:ここでは、AutoFixtureがSUTを作成しています

  • myInterface.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<string>())).Throws<System.IO.IOException>();ここでは、Getメソッドが呼び出されたときに例外をスローするように依存関係を構成しています。このインターフェイスの残りのメソッドは構成されていないため、アクセスしようとすると予期しない例外が発生します

  • sut.Invoking(x => x.TransferFiles(myInterface.Object, It.IsAny<string>(), It.IsAny<string>())).ShouldThrow<System.IO.IOException>();:そして最後に、SUTをテストするとき、この行はFluenAssertionsライブラリを使用し、SUTからTransferFilesrealメソッドを呼び出すだけです。 およびパラメーターとして、モックされたIFileConnectionを受け取るため、SUT TransferFilesメソッドの通常のフローで_IFileConnection.Get_を呼び出すたびに、モックされたオブジェクト構成された例外のスローを呼び出します。これは、SUTが例外を正しく処理していることをアサートする時間です。この場合、ShouldThrow<System.IO.IOException>()(FluentAssertionsから)を使用して例外がスローされたことを保証しますとしょうかん)

推奨される参考文献:

http://martinfowler.com/articles/mocksArentStubs.html

http://misko.hevery.com/code-reviewers-guide/

http://misko.hevery.com/presentations/

http://www.youtube.com/watch?v=wEhu57pih5w&feature=player_embedded

http://www.youtube.com/watch?v=RlfLCWKxHJ0&feature=player_embedded

5
Jupaol