【问题标题】:Should Dispose methods be unit tested?Dispose 方法应该进行单元测试吗?
【发布时间】:2010-07-15 19:55:37
【问题描述】:

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

【问题讨论】:

  • 像 C# 中一样处理方法还是您的意思是 C++ 风格的析构函数?
  • C# 风格的 Dispose 方法

标签: c# unit-testing 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 命名空间对 HttpContext 类所做的 - 即使用所有虚拟方法定义 HttpContextBase 类,这些虚拟方法只是将方法调用委托给真正的 @ 987654329@班级。

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

【讨论】:

  • 理想情况下,还应该测试对 Dispose 的重入和并发调用(在多线程应用程序中)。
【解决方案2】:

当然不会受伤。客户端代码可能会在处理掉您的类的对象后尝试使用它。如果您的类由其他 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.
}

【讨论】:

  • 我没有看到您已经证明发生了任何有用的事情。为了值得,您需要检查资源是否实际被释放。如果您正在访问一个文件,那么您应该检查您现在是否可以打开它进行修改。我不确定您将如何测试数据库连接是否实际关闭。
  • @Kirk,就像我说的,内部状态通常不是单元测试的一部分。只有从类外部可见的部分才是编写单元测试的对象。
  • 只是一个关于您的测试代码的快速问题,您的意思是在测试之前调用对象的 dispose 方法,还是发生了一些我看不到处理对象的事情?
  • @sbenderli,using 语句是在退出范围时处理对象的简写。
  • 啊,是的,愚蠢的问题。谢谢!
【解决方案3】:

如果您的班级创建并使用非托管资源,那么您绝对应该确保 Dispose 按您期望的方式工作 - 尽管由于您将要使用的箍类型,它可能更像是一个集成测试必须跳过。

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

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

【讨论】:

    【解决方案4】:

    是的——如果你的情况需要你实现一个 Dispose 函数——你最好确保它按照你的想法去做!

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

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

    【讨论】:

      【解决方案5】:

      作为一个实用提示(因为是的,你应该测试Dispose())我的经验是,有两种方法可以轻松做到这一点。

      IDisposer

      第一个遵循 Igor 接受的答案 - 注入类似 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(尽管您应该改用测试框架,例如起订量)。

      【讨论】:

        猜你喜欢
        • 2015-02-26
        • 1970-01-01
        • 1970-01-01
        • 2010-09-29
        • 1970-01-01
        • 1970-01-01
        • 2016-11-16
        • 2011-01-01
        • 1970-01-01
        相关资源
        最近更新 更多