2012-04-25 2 views
26

У меня есть Transfer класса, упростили это выглядит следующим образом:Издевательствовать метод выброса исключения (moq), но в противном случае действовать как насмешливый объект?

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

Реальный класс должен обрабатывать System.IO.IOException, который бросается, когда классы классов IFileConnection теряет связь с пультом дистанционного управления, отправляет электронные письма, а что нет.

Я хотел бы использовать Moq для создания Transfer класса, а также использовать его в качестве моего конкретного Transfer класса во всех свойствах и методах, кроме случаев, когда метод GetFile вызывается - то я хочу, чтобы бросить System.IO.IOException и убедитесь, Transfer класс обрабатывает его правильно.

Я использую подходящий инструмент для работы? Правильно ли я это делаю? И как мне написать настройку для этого модульного теста для NUnit?

ответ

7

Это, как мне удалось сделать то, что я пытаюсь сделать:

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

Если есть проблема с этим методом, я хотел бы знать; другие ответы предполагают, что я делаю это неправильно, но это именно то, что я пытался сделать.

49

Вот как вы можете издеваться ваш 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); 

Update:

Прежде всего вам должны издеваться над вашими зависимостями, а не за класс, который вы тестируете (в этом случае класс переноса). Указание этих зависимостей в вашем конструкторе облегчает просмотр того, какие службы должен работать ваш класс. Это также позволяет заменить их подделками при написании модульных тестов. На данный момент невозможно заменить эти свойства подделками.

Поскольку вы устанавливаете эти свойства, используя другую зависимость, я бы написал так:

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 издевается, что делает то, что вы хотите.

+0

Хм. Не то, что я ожидал, и теперь я вижу, что я слишком упростил свой класс «Передача». Можете ли вы посмотреть на изменения, внесенные мной в класс «Перенос»? Это немного напоминает мой код. Свойства отображаются в классе, но я сделал это в основном, поэтому я мог проверить условие исключения. Это то, что я пытался сделать не так? –

+0

@JeremyHolovacs Я обновил свой ответ. –

+0

Хм. То, что я считал простым, кажется, становится меньше. Позвольте мне спросить вас об этом: имеет ли смысл создавать тестовый класс, который наследует от «Transfer» и переопределяет метод для целей тестирования? Возможно, насмехается не правильная технология для использования в этом сценарии, и я пытаюсь засунуть квадратный штифт в круглое отверстие? –

2

Я думаю, что это то, что вы хотите, я уже тестировал этот код и работает

Инструменты, используемые являются: (все эти инструменты могут быть загружены в виде пакетов 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. Когда я говорю о замене, я имею в виду использование макета, манекена, макета и т. Д. В зависимости от ваших потребностей.

В этом случае ваша внешняя зависимость равна IFileConnection, поэтому вам нужно создать макет для этой зависимости и настроить ее для исключения исключения , то просто обратитесь к SUT реальный метод и утверждать, что ваш метод обрабатывает исключение, как ожидалось

  • var fixture = new Fixture().Customize(new AutoMoqCustomization());: Этот Linie инициализирует новый объект арматуре (Autofixture библиотека), этот объект используется для создания SUT без явного иметь беспокоиться о параметрах конструктора, поскольку они создаются автоматически или издеваются, в этом случае с использованием Moq

  • var myInterface = fixture.Freeze<Mock<IFileConnection>>();: Замораживает зависимость IFileConnection. «Замораживание» означает, что Autofixture будет всегда использовать эту зависимость, если ее спросят, как одиночный тон для простоты.Но самое интересное в том, что мы создаем Mock этой зависимости, вы можете использовать все методы 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>(); : И, наконец, время t Эст ваш SUT, эта линия использует библиотеку FluenAssertions, и он просто называет TransferFilesреальный метод из SUT и в качестве параметров она принимает высмеивал IFileConnection так всякий раз, когда вы вызываете IFileConnection.Get в нормальном потоке вашего метода SUT TransferFiles, то Передразнивало объект будет вызывать сброс сконфигурированного исключения, и настало время утверждать, что ваш 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

+0

Я не думаю, что это делает то, что мне нужно для этого. Мне нужно выбросить исключение в середине метода 'Transfer.TransferFiles()', разрешив вызову метода 'Transfer.TransferFiles()' вызывать исключение в методе 'GetFile()'. Я думаю (если я читаю это право), это вводит 'IFileConnection' во время выполнения, которое будет написано. Я ошибаюсь? –

Смежные вопросы