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

Следует ли тестировать методы Dispose?

Я использую С#. Рекомендуется ли unit test утилизировать методы? Если да, то почему и как следует проверять эти методы?

4b9b3361

Ответ 1

Да, но это может быть сложно. В реализации Dispose есть две вещи:

Освобождены неуправляемые ресурсы.

В этом случае довольно сложно проверить, что код называется, например, Marshal.Release. Возможное решение заключается в том, чтобы ввести объект, который может выполнить удаление, и передать его в процессе тестирования. Что-то в этом роде:

interface ComObjectReleaser {
    public virtual Release (IntPtr obj) {
       Marshal.Release(obj);
    }
}

class ClassWithComObject : IDisposable {

    public ClassWithComObject (ComObjectReleaser releaser) {
       m_releaser = releaser;
    }

    // Create an int object
    ComObjectReleaser m_releaser;
    int obj = 1;
    IntPtr m_pointer = Marshal.GetIUnknownForObject(obj);

    public void Dispose() {
      m_releaser.Release(m_pointer);
    }
}

//Using MOQ - the best mocking framework :)))
class ClassWithComObjectTest {

    public DisposeShouldReleaseComObject() {
       var releaserMock = new Mock<ComObjectReleaser>();
       var target = new ClassWithComObject(releaserMock);
       target.Dispose();
       releaserMock.Verify(r=>r.Dispose());
    }
}

Метод других классов Dispose называется

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

Один из способов заключается в том, чтобы обернуть эти другие объекты в макетируемой оболочке, аналогично тому, что System.Web.Abstractions namespace делает для класса HttpContext, т.е. определяет класс HttpContextBase со всеми виртуальными методами, которые просто делегируют вызовы методов реальным HttpContext.

Дополнительные идеи о том, как сделать что-то подобное, смотрите Проект System.IO.Abstractions.

Ответ 2

Если ваш класс создает и работает с неуправляемыми ресурсами, вы должны обязательно убедиться, что Dispose работает так, как вы ожидаете, - хотя можно утверждать, что это скорее интеграционный тест из-за типа обручей, которые вы собираетесь должны прыгать.

Если ваш класс создает/использует только управляемые ресурсы (то есть они реализуют IDisposable), тогда вам действительно нужно убедиться, что метод Dispose на этих ресурсах вызывается в нужное время - если вы используете какую-либо форму DI, тогда вы может вводить макет и утверждать, что Dispose был вызван.

Посмотрите на сложность своих методов размещения - если они всего лишь на пару строк с возможно одним условием, спросите себя, действительно ли есть преимущество в тестировании модулей.

Ответ 3

Конечно, это не повредит. Клиентский код может попытаться использовать объект вашего класса после его удаления. Если ваш класс состоит из других объектов IDisposable, вы всегда должны бросать исключение ObjectDisposedException, если оно находится в состоянии, которое больше невозможно использовать.

Конечно, вы должны тестировать только внешнее состояние вашего объекта. В приведенном ниже примере я сделал свойство Disposed external, чтобы дать мне состояние.

Рассмотрим:

internal class CanBeDisposed : IDisposable
{
    private bool disposed;
    public bool Disposed
    {
        get
        {
            if (!this.disposed)
                return this.disposed;
            throw new ObjectDisposedException("CanBeDisposed");
        }
    }

    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                //// Dispose of managed resources.
            }
            //// Dispose of unmanaged resources.
            this.disposed = true;
        }
    }
}

Итак, как бы я это испытал, это:

CanBeDisposed cbd;

using (cbd = new CanBeDisposed())
{
    Debug.Assert(!cbd.Disposed); // Best not be disposed yet.
}

try
{
    Debug.Assert(cbd.Disposed); // Expecting an exception.
}
catch (Exception ex)
{
    Debug.Assert(ex is ObjectDisposedException); // Better be the right one.
}

Ответ 4

Большое да - если ваша ситуация требует, чтобы вы реализовали функцию Dispose - вам лучше убедиться, что она делает то, что вы думаете!

Например, у нас есть классы, которые координируют задачи базы данных (считайте пакеты SSIS, но с SqlConnection и SqlCommand и SqlBulkCopy и т.д.).

Если я неправильно реализую свой Dispose, я мог бы иметь незафиксированное SqlTransaction или оборванное SqlConnection. Это было бы ОЧЕНЬ плохо, если бы я запускал несколько экземпляров этих задач базы данных последовательно.