Подтвердить что ты не робот

Издевательствовать метод для исключения исключений (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);
}

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

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

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

Вот как вы можете издеваться над FileConnection

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

Затем создайте экземпляр класса Transfer и используйте mock в вызове метода

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

Таким образом, вы можете mock internalConfig и заставить его возвращать макеты IFileConnection, который делает то, что вы хотите.

Ответ 3

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

Используемые инструменты: (все эти инструменты можно загрузить как пакеты 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>();

Отредактировано:

Позвольте мне объяснить:

Когда вы пишете тест, вы должны точно знать, что вы хотите проверить, это называется: "subject under test (SUT)", если мое понимание правильно, в этом случае ваш SUT: Transfer

Итак, имея в виду это, вы не должны издеваться над SUT, если вы замените SUT, тогда вы фактически не будете тестировать реальный код

Когда ваш SUT имеет внешние зависимости (очень распространенные), вы должны их заменить, чтобы протестировать в изоляции ваш SUT. Когда я говорю о замене, я имею в виду использование макета, манекена, макета и т.д. В зависимости от ваших потребностей.

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

  • var fixture = new Fixture().Customize(new AutoMoqCustomization());: этот linie инициализирует новый объект Fixture (библиотека 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>();: И, наконец, время проверки вашего 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