繁体   English   中英

是否应该对 Dispose 方法进行单元测试?

[英]Should Dispose methods be unit tested?

我正在使用 C#。 是否建议对处理方法进行单元测试? 如果是这样,为什么,以及应该如何测试这些方法?

当然伤不起。 客户端代码可能会在处理掉类的对象后尝试使用它。 如果您的类由其他IDisposable对象组成,并且它处于不再可用的状态,则您应该始终抛出ObjectDisposedException异常。

当然,您应该只测试对象的外部状态。 在下面的示例中,我将属性Disposed外部以提供状态。

考虑:

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.
}

是的,但可能很难。 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命名空间为HttpContext类所做的 - 即定义HttpContextBase类和所有虚拟方法,这些虚拟方法只是将方法调用委托给真正的HttpContext类。

有关如何执行此类操作的更多想法,请查看System.IO.Abstractions项目。

如果您的类创建并使用非托管资源,那么您绝对应该确保 Dispose 像您期望的那样工作 - 尽管可以说它更像是一个集成测试,因为您将不得不跳过的箍类型通过。

如果您的类只创建/使用托管资源(即它们实现 IDisposable ),那么您真正需要确保的是在正确的时间调用这些资源上的 Dispose 方法 - 如果您使用某种形式的 DI,那么您可以注入一个模拟并断言调用了 Dispose。

看看你的 dispose 方法的复杂性——如果它们只有几行长,可能有 1 个条件,问问自己是否真的对它们进行单元测试有好处。

大是 - 如果您的情况需要您实现 Dispose 功能 - 您最好确保它按照您的想法进行!

例如,我们有协调数据库任务的类(想想 SSIS 包,但有 SqlConnection 和 SqlCommand 和 SqlBulkCopy 等)。

如果我没有正确地实现我的 Dispose,我可能会有一个未提交的 SqlTransaction 或悬空的 SqlConnection。 如果我连续运行这些数据库任务的多个实例,这将是非常糟糕的。

作为一个实用技巧(因为是的,你应该测试Dispose() )我的经验是有两种方法可以做到这一点而不会太麻烦。

IDisposer

第一个遵循 Igor 接受的答案 - 注入IDisposer类的IDisposer ,以便您可以调用

public void Dispose()
{
    _disposer.Release(_disposable);
}

在哪里

public interface IDisposer
{
    void Release(IDisposable disposable);
}

然后你需要做的就是模拟IDisposer并断言它被调用一次并且你是金子。

工厂

第二个,也是我个人最喜欢的,是有一个工厂来制造你需要测试处理的东西。 这仅在工厂生成可模拟类型(接口、抽象类)时才有效,但是,嘿,情况几乎总是如此,尤其是对于要处理的内容。 出于测试目的,模拟工厂,但让它生成您要测试处理的事物的模拟实现。 然后你可以直接在你的模拟上断言对Dispose调用。 类似的东西

public interface IFooFactory
{
    IFoo Create(); // where IFoo : IDisposable
}

public class MockFoo : IFoo
{
    // ugly, use something like Moq instead of this class
    public int DisposalCount { get; privat set; }

    public void Dispose()
    {
        DisposalCount++;
    }
}

public class MockFooFactory
{
    public MockFoo LatestFoo { get; private set; }

    public IFoo Create()
    { 
        LatestFoo = new MockFoo();
        return LatestFoo;
    }
}

现在您可以随时要求工厂(将在您的测试中可用)为您提供最新的MockFoo ,然后您处理外部事物并检查DisposalCount == 1 (尽管您应该使用测试框架代替,例如 Moq) .

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM