Я использую С#. Рекомендуется ли unit test утилизировать методы? Если да, то почему и как следует проверять эти методы?
Следует ли тестировать методы Dispose?
Ответ 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. Это было бы ОЧЕНЬ плохо, если бы я запускал несколько экземпляров этих задач базы данных последовательно.