2012-04-25 16 views
26

Mam klasy Transfer, uproszczone wygląda to tak:Krzesanie metoda rzucić wyjątek (moq), ale poza tym zachowywać się jak wyśmiewany obiekt?

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

Uproszczona wersja interfejsu IFileConnection wygląda następująco:

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

Prawdziwa klasa ma obsługiwać System.IO.IOException który jest rzucane, gdy konkretne klasy IFileConnection tracą łączność z pilotem, wysyłając e-maile, a co nie.

Chciałbym użyć Min utworzyć klasę Transfer i używać go jako mojego betonowej Transfer klasy we wszystkich właściwości i metod, z wyjątkiem przypadku, gdy metoda GetFile jest wywoływana - to chcę to rzucić System.IO.IOException i upewnij się, że Klasa Transfer obsługuje ją poprawnie.

Czy używam odpowiedniego narzędzia do pracy? Czy podążam tą drogą we właściwy sposób? I jak mam napisać konfigurację dla tego testu jednostkowego dla NUnit?

Odpowiedz

7

W ten sposób udało mi się zrobić to, co starałem się zrobić:

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

Jeśli występuje problem z tą metodą, chciałbym wiedzieć; inne odpowiedzi sugerują, że robię to źle, ale właśnie to próbowałem zrobić.

49

Oto jak można kpić Twój FileConnection

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

Następnie zainicjować klasę transfer i wykorzystać makiety w wywołaniu metody

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

Aktualizacja:

Przede wszystkim muszą kpić tylko z twoich zależności, a nie z klasy, którą testujesz (w tym przypadku klasa Transfer). Podanie tych zależności w swoim konstruktorze ułatwia sprawdzenie, jakie usługi potrzebuje Twoja klasa. Umożliwia także zastąpienie ich podróbkami podczas pisania testów jednostkowych. W tej chwili niemożliwa jest zamiana tych właściwości na podróbki.

Od ustawiania tych właściwości przy użyciu innego uzależnienia, chciałbym napisać to tak:

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

ten sposób można mock internalConfig i sprawiają, że powrót mocks IFileConnection że robi to, co chcesz.

+0

Hmm. Nie tego oczekiwałem, a teraz widzę, jak za bardzo upraszczałem moją klasę "Transfer". Czy możesz spojrzeć na zmiany, które wprowadziłem w klasie "Transfer"? Przypomina to nieco mój kod. Właściwości są ujawnione w klasie, ale zrobiłem to głównie, więc mogłem przetestować warunek wyjątku. Czy to, co próbowałem zrobić w niewłaściwy sposób? –

+0

@JeremyHolovacs Zaktualizowałem swoją odpowiedź. –

+0

Hmm. To, co wydawało mi się proste, wydaje się coraz mniej. Pozwól, że Cię o to zapytam: czy bardziej sensownym byłoby stworzenie klasy testowej, która dziedziczy po 'Transfer' i nadpisuje metodę w celach testowych? Być może kpina nie jest odpowiednią technologią do zastosowania w tym scenariuszu, a ja próbuję wepchnąć kwadratowy kołek do okrągłej dziury? –

2

myślę, że to, co chcesz, ja już przetestowane ten kod i działa

Narzędzia wykorzystywane są następujące: (wszystkie te narzędzia mogą być pobrane jako pakiety 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>(); 

Zmieniano:

Pozwól mi wyjaśnić:

Podczas pisania testu, trzeba wiedzieć dokładnie, co chcesz przetestować, to się nazywa: „osobnik badany (SUT) ", jeśli moje zrozumienie jest poprawne, w tym przypadku Twoja SUT jest: Transfer

Z tego względu nie powinieneś kpić ze swojej SUT, jeśli zastąpisz SUT, wtedy nie będziesz faktycznie testował prawdziwego kodu

Kiedy twój SUT ma zewnętrzne zależności (bardzo często), musisz je zastąpić, aby przetestować w izolację swojej SUT. Kiedy mówię jako zastępca, mam na myśli, że używam maniaka, manekina, mocku itp. W zależności od twoich potrzeb.

W tym przypadku twoja zewnętrzna zależność to IFileConnection, więc musisz utworzyć makietę dla tej zależności i skonfigurować ją tak, aby wygenerowała wyjątek , a potem po prostu zadzwonić do SUT prawdziwą metodę i potwierdzą swoją metoda obsługuje wyjątek jako oczekiwaną

  • var fixture = new Fixture().Customize(new AutoMoqCustomization());: Ten linie inicjuje nowy obiekt Spotkań (Autofixture Library), cel ten jest używany do tworzenia SUT bez konieczności jawnie martwić się o parametry konstruktora, ponieważ są one tworzone automatycznie lub wyśmiewane, w tym przypadku przy użyciu Moq

  • var myInterface = fixture.Freeze<Mock<IFileConnection>>();: Blokuje zależność IFileConnection. Freeze oznacza, że ​​Autofixture użyje zawsze tej zależności na żądanie, jak singleton dla uproszczenia.Ale ciekawe jest to, że mamy do tworzenia makiety tego uzależnienia, można korzystać ze wszystkich metod Min, ponieważ jest to prosty Moq obiekt

  • var sut = fixture.CreateAnonymous<Transfer>();: Tutaj AutoFixture jest stworzenie SUT dla nas

  • myInterface.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<string>())).Throws<System.IO.IOException>(); Tutaj konfigurowania zależność rzucić wyjątek, gdy metoda Get jest wywoływana, resztę metod z tego interfejsu nie są skonfigurowane, więc jeśli spróbujesz do nich dostęp otrzymasz nieoczekiwany wyjątek

  • sut.Invoking(x => x.TransferFiles(myInterface.Object, It.IsAny<string>(), It.IsAny<string>())).ShouldThrow<System.IO.IOException>(); : I wreszcie czas na t est Twój SUT, linia ta wykorzystuje bibliotekę FluenAssertions, i to właśnie nazywa TransferFilesprawdziwą metodę z SUT i jako parametry otrzymaniu szydzili IFileConnection więc kiedy tylko wywołać IFileConnection.Get w normalnym przepływie metodę SUT TransferFiles The szydzili obiekt będzie wywoływał wyrzucanie skonfigurowanego wyjątku i jest to czas, aby potwierdzić, że twoja SUT obsługuje poprawnie wyjątek, w tym przypadku, zapewniam tylko, że wyjątek został zgłoszony przy użyciu ShouldThrow<System.IO.IOException>() (z biblioteki FluentAssertions)

Zalecane referencje:

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

+0

Nie sądzę, że zrobi to, czego bym potrzebował. Muszę rzucić wyjątek w środku metody 'Transfer.TransferFiles()', pozwalając na wywołanie metody 'Transfer.TransferFiles()' wyjątku w metodzie 'GetFile()'. Myślę, że (jeśli czytam to dobrze) to wstrzykuje 'IFileConnection' w czasie wykonywania, który zostanie zapisany. Czy się mylę? –

Powiązane problemy