webentwicklung-frage-antwort-db.com.de

Eine Methode zum Auslösen einer Ausnahme verspotten (moq), aber ansonsten wie das verspottete Objekt verhalten?

Ich habe eine Transfer Klasse, vereinfacht sieht das so aus:

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

Die vereinfachte Version der IFileConnection -Oberfläche sieht folgendermaßen aus:

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

Die reale Klasse soll einen System.IO.IOException Verarbeiten, der ausgelöst wird, wenn die konkreten Klassen IFileConnection die Verbindung zur Remote-Klasse verlieren und E-Mails versenden und was nicht.

Ich möchte Moq verwenden, um eine Transfer -Klasse zu erstellen und sie in allen Eigenschaften und Methoden als meine konkrete Transfer -Klasse zu verwenden, außer wenn die GetFile -Methode aufgerufen wird Ich möchte, dass es einen System.IO.IOException Auslöst und die Klasse Transfer richtig behandelt.

Benutze ich das richtige Werkzeug für den Job? Gehe ich das richtig an? Und wie würde ich das Setup für diesen Komponententest für NUnit schreiben?

47
Jeremy Holovacs

So habe ich es geschafft, was ich versucht habe:

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

Wenn es ein Problem mit dieser Methode gibt, würde ich gerne wissen; Die anderen Antworten deuten darauf hin, dass ich das falsch mache, aber genau das habe ich versucht.

9
Jeremy Holovacs

So kannst du dein FileConnection verspotten

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

Instanziieren Sie dann Ihre Transfer-Klasse und verwenden Sie den Mock in Ihrem Methodenaufruf

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

pdate:

Zunächst müssen Sie nur Ihre Abhängigkeiten verspotten, nicht die Klasse, die Sie testen (in diesem Fall Klasse übertragen). Wenn Sie diese Abhängigkeiten in Ihrem Konstruktor angeben, können Sie leicht erkennen, welche Dienste Ihre Klasse benötigt, um zu funktionieren. Es macht es auch möglich, sie durch Fälschungen zu ersetzen, wenn Sie Ihre Komponententests schreiben. Im Moment ist es unmöglich, diese Eigenschaften durch Fälschungen zu ersetzen.

Da Sie diese Eigenschaften mithilfe einer anderen Abhängigkeit festlegen, würde ich Folgendes schreiben:

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

Auf diese Weise können Sie internalConfig verspotten und IFileConnection-Verspottungen zurückgeben lassen, die das tun, was Sie wollen.

65

Ich denke, das ist, was Sie wollen, ich habe diesen Code bereits getestet und funktioniert

Folgende Tools werden verwendet: (Alle diese Tools können als Nuget-Pakete heruntergeladen werden.)

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>();
_

Bearbeitet:

Lassen Sie mich erklären:

Wenn Sie einen Test schreiben, müssen Sie genau wissen, was Sie testen möchten. Dies wird als "Testobjekt (SUT)" bezeichnet. Wenn ich das richtig verstehe, lautet Ihr SUT in diesem Fall: Transfer

Aus diesem Grund sollten Sie Ihr SUT nicht verspotten. Wenn Sie Ihr SUT ersetzen, würden Sie den tatsächlichen Code nicht testen

Wenn Ihr SUT externe Abhängigkeiten hat (sehr häufig), müssen Sie diese ersetzen, um eine Isolation Ihres SUT zu testen. Wenn ich "Ersatz" sage, beziehe ich mich auf die Verwendung eines Mocks, Dummys, Mocks usw., je nach Ihren Anforderungen

In diesem Fall lautet Ihre externe Abhängigkeit IFileConnection, daher müssen Sie für diese Abhängigkeit einen Mock erstellen und ihn so konfigurieren, dass die Ausnahme ausgelöst wird. Rufen Sie dann einfach Ihre echte SUT-Methode auf und stellen Sie sicher, dass Ihre Methode die Ausnahme wie erwartet behandelt

  • var fixture = new Fixture().Customize(new AutoMoqCustomization());: Diese Linie initialisiert ein neues Fixture-Objekt (Autofixture-Bibliothek). Dieses Objekt wird verwendet, um SUTs zu erstellen, ohne sich explizit um die Konstruktorparameter kümmern zu müssen, da sie in diesem Fall automatisch erstellt oder verspottet werden mit Moq

  • var myInterface = fixture.Freeze<Mock<IFileConnection>>();: Dies friert die Abhängigkeit von IFileConnection ein. Einfrieren bedeutet, dass Autofixture immer diese Abhängigkeit verwendet, wenn Sie gefragt werden, wie ein Singleton zur Vereinfachung. Der interessante Teil ist jedoch, dass wir ein Mock dieser Abhängigkeit erstellen. Sie können alle Moq-Methoden verwenden, da dies ein einfaches Moq-Objekt ist

  • var sut = fixture.CreateAnonymous<Transfer>();: Hier erstellt AutoFixture das SUT für uns

  • myInterface.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<string>())).Throws<System.IO.IOException>(); Hier konfigurieren Sie die Abhängigkeit, um bei jedem Aufruf der Get -Methode eine Ausnahme auszulösen. Die übrigen Methoden dieser Schnittstelle werden nicht konfiguriert. Wenn Sie also versuchen, auf sie zuzugreifen, müssen Sie dies tun wird eine unerwartete Ausnahme bekommen

  • sut.Invoking(x => x.TransferFiles(myInterface.Object, It.IsAny<string>(), It.IsAny<string>())).ShouldThrow<System.IO.IOException>();: Und schließlich verwendet diese Zeile zum Testen Ihres SUT die FluenAssertions-Bibliothek und ruft nur die reale Methode TransferFiles vom SUT auf und als Parameter erhält es das verspottete IFileConnection, wenn Sie also im normalen Ablauf Ihrer SUT TransferFiles -Methode das verspottete Objekt _IFileConnection.Get_ aufrufen wird aufgerufen, indem die konfigurierte Ausnahme ausgelöst wird. In diesem Fall wird nur sichergestellt, dass die Ausnahme mithilfe der Funktion ShouldThrow<System.IO.IOException>() (aus den FluentAssertions) ausgelöst wurde Bibliothek)

Referenzen empfohlen:

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