Как утилизировать макет объекта? - PullRequest
0 голосов
/ 27 июня 2018

У меня есть фиктивный объект, который запускает таймер и должен быть утилизирован в моем тесте. Как правильно расположить макет объекта? В моем классе, чтобы быть высмеянным, у меня есть:

protected virtual void Dispose(bool disposing)
{
    if (!_disposed)
    {
        if (disposing)
        {
            // Stop and dispose timer here.
        }

        _disposed = true;
    }
}

public void Dispose()
{
    Dispose(true);
}

Итак, теперь в моем тесте мне нужно смоделировать объект, использовать его, а затем убедиться, что он утилизируется. Я знаю, что могу просто установить CallBase = true, но я не уверен, что это правильный (отраслевой стандарт) способ действий:

[TestMethod]
public void TestSomething()
{
    var mock = new Mock<ObjectWithTimer>() { CallBase = true };

    using (var foo = mock.Object)
    {
        foo.DoSomething(); // This consequently starts a timer.
    } // Here, Dispose() will be called.
}

Когда блок использования заканчивается, вызывается Dispose(), который вызывает базу Dispose(bool). Однако возможно ли это сделать, не добавляя CallBase = true к моему макету? Это не идеально, когда мне нужно использовать mock для переопределения других методов.

Ответы [ 2 ]

0 голосов
/ 27 июня 2018

Используя new Mock<ObjectWithTimer>(), ваш код не полностью отделен от реализации этого класса, так как этот метод дает фиктивный класс, производный от вашей конкретной реализации, который (как вы ясно разработали) на самом деле не то, что Вы хотите, чтобы ваши издевательства делали.

Создать интерфейс для ObjectWithTimer. Интерфейс должен включать все, что является общедоступным об этом классе. Так как класс IDisposable, ваш интерфейс должен быть производным от этого интерфейса. Измените код, который зависит от этого класса, чтобы он зависел от интерфейса, и измените тест, чтобы смоделировать интерфейс, а не класс.

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

Вызывающий код теперь полностью отделен от реализации этого интерфейса, поэтому больше не имеет значения, как реализован конкретный класс (ObjectWithTimer).

0 голосов
/ 27 июня 2018

Может быть, проблема решится, если вы потеряете связь между двумя классами, так что макет будет более общим. Это можно сделать, если ваш тестируемый класс не использует ObjectWithTimer напрямую, а интерфейс, который реализуется ObjectWithTimer.

Пример:

public interface IObjectWithTimer : IDisposable
{
    void DoSomething();
}

public class ObjectWithTimer : IObjectWithTimer
{
    // ...
}

public class ClassUnderTest
{ 
    public ClassUnderTest(IObjectWithTimer timer)
    {
        // ...
    }

    public void ThisShouldCallDisposeOnTimer()
    {
        // ...
    }
}

Тогда ваш тестовый код выглядит так:

[TestMethod]
public void ShouldCallDispose()
{
    var mock = new Mock<IObjectWithTimer>();
    var classUnderTest = new ClassUnderTest(mock.Object);

    classUnderTest.ThisShouldCallDisposeOnTimer();

    mock.Verify(x => x.Dispose(), Times.Once());
}

Таким образом, ваш тест полностью отделен от логики удаления таймера, что на самом деле не то, что вы хотите проверить.

...