【问题标题】:How to unit test Dispose() using reflection?如何使用反射对 Dispose() 进行单元测试?
【发布时间】:2016-05-16 18:16:04
【问题描述】:

我想为实现 IDisposable 的类编写单元测试。该类有许多也实现 IDisposable 的私有字段。在我的测试中,我想验证当我调用Dispose() 时,它在其所有IDisposable 字段上正确调用Dispose()。本质上,我希望我的单元测试看起来像这样:

var o = new ObjectUnderTest();
o.Dispose();
Assert.IsFalse(ObjectHasUndisposedDisposables(o));

我正在考虑使用反射来实现这一点。这似乎是一个相当普遍的要求,但我找不到任何例子。

有人试过吗?

编辑——我不想将 Disposables 注入到被测类中。

【问题讨论】:

  • o.IsDisposed == true ?在你的情况下可能:Assert.IsTrue(o.IsDispoed);
  • 我推荐使用依赖注入容器。管理一次性依赖项只是好处之一。如果您的类创建了更多需要跟踪和处理的依赖项,那么它不依赖于抽象。使用 DI,您的课程只依赖于IWhatever。它不知道也不关心这个类是否是一次性的。

标签: c# unit-testing reflection dispose assert


【解决方案1】:

在不重构代码的情况下验证您正在寻找的行为的唯一方法是使用代码编织工具,例如; Typemock Isolator、MsFakes 等...

以下 sn-p 显示了使用 MsFakes 验证行为的方法:

[TestMethod]
public void TestMethod1()
{
    var wasCalled = false;
    using (ShimsContext.Create())
    {
        ForMsFakes.Fakes.ShimDependency.AllInstances.Dispose = dependency =>
        {
            wasCalled = true;
        };

        var o = new ObjectUnderTest();

        o.Dispose();
    }

    Assert.IsTrue(wasCalled);
}

public class Dependency : IDisposable
{
    public void Dispose() {}
}

public class ObjectUnderTest: IDisposable
{
    private readonly Dependency _d = new Dependency();

    public void Dispose()
    {
        _d.Dispose();
    }
}

【讨论】:

  • 这样做的一个缺点是它不是通用的。据我所知,在运行时没有自动生成 shim 的方法,因此您需要为类中的每个一次性类型手动生成 ShimXxxxxx。如果稍后使用新的一次性类型更新该类,则单元测试将不会检查它。这会带走单元测试一半的价值,因为它不会在添加新类型时保护您。但是有一个解决方法...
  • var fieldCount = typeof(ClassUnderTest).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Where(x=>typeof(IDisposable).IsAssignableFrom(x.FieldType)).Count(); Assert.AreEqual(KnownFieldCount, fieldCount, "The number of known fields did not match the class. Update the unit test with new dispose classes as needed"); 作为您的测试之一。如果有人用不同数量的一次性用品更新类,这将导致测试失败,但是如果您同时删除一个并添加一个,它不会标记。
【解决方案2】:

没有通用的方法来处理这个问题。 IDisposeable 接口不需要您跟踪是否调用了 dispose。

我能想到的唯一可以处理这个问题的方法是,如果所有这些一次性类都使用依赖注入进行注入。如果你这样做了,你可以模拟注入的类并跟踪是否调用了 dispose。

    [TestMethod]
    public void TestAllDisposeablesAreDisposed()
    {
        var fooMock = new Mock<IFoo>();
        fooMock.Setup(x=>x.Dispose())
            .Verifiable("Dispose never called on Foo");

        var barMock = new Mock<IBar>();
        barMock.Setup(x => x.Dispose())
            .Verifiable("Dispose never called on Bar");

        using (var myClass = new MyClass(fooMock.Object, barMock.Object))
        {
        }

        fooMock.Verify();
        barMock.Verify();
    }

【讨论】:

  • 这是一个很好的答案。如果您正在处理类中的依赖项,请编写测试来验证这一点。如果您将处置逻辑移到课堂之外,它们将会中断,这是一件好事。
  • 另外,我不介意将我正在使用的所有 Disposables 包装在另一个界面(比如 ITrackedDisposable)中,该界面会跟踪它们的处理时间。
【解决方案3】:

您是要测试您的IDisposable 行为是否在给定类中有效,还是要测试管理您的一次性类的东西是否实际调用IDisposable?这是有效的,如果你不确定它是否被调用并且你想确定。 (我已经为此编写了测试。)

为此,您可以像这样创建一个简单的类:

public class DisposeMe : IDisposable
{
    public bool Disposed {get;set;}
    public void Dispose()
    {
        Disposed = true;
    }
}

然后,您可以断言 Disposed 在您期望它已被处置时为真。

例如,当我从 DI 容器创建类时,我使用过它,并且我想确认当容器创建的类被释放时,它的一次性依赖项会被释放。我无法在实际的具体类中跟踪它,但我可以测试 DI 容器正在做我认为它应该做的事情。 (这不是我一遍又一遍地编写的测试。例如,一旦我确认了 Windsor 的预期行为,我就不会继续为它编写单元测试。)

【讨论】:

  • 我想测试一下,当我调用 ObjectUnderTest.Dispose() 时,它会在其所有组合的 Disposables 上正确调用 Dispose()。我意识到我可以通过注入一次性用品来做到这一点,但我认为这是一个糟糕的设计,因为我的界面必须由于实现的变化而改变。似乎反射将是实现这一目标的一种更清洁的方式,并且可以在任何 IDisposable 上一次又一次地重用。
  • @user6118784 反映了什么?任何时候您想使用反射将其替换为“公开所有字段”,因此您的句子变为“似乎 公开所有字段 将是实现此目标的更清洁的方式,并且可以重用一次又一次地在任何 IDisposable 上”。公开这些字段将如何解决您知道何时有人在其中一个字段上调用 ​​.Dispose() 的问题?您必须拦截调用才能检测到它,并且在不涉及像 IL 重写这样真正奇特的东西的情况下,您必须使用注入来完成它。
  • 回答您的问题: 1) ObjectUnderTest 的私有字段的反射。 2)如果我将一次性字段公开,我的单元测试可以轻松测试它们是否已被处理 - 我会将所有一次性字段包装在 ITrackedDisposable 之类的东西中(它跟踪何时在它们上调用 Dispose() )以实现这个。
【解决方案4】:

如果你不愿意在构造函数中使用依赖注入,因为这会迫使你改变你的接口,那么你可以通过属性注入你的依赖。如果没有注入任何内容,请给您的属性一个默认值,这样当您使用该类时,您不必记住传递真正的实现

例如

public class ObjectUnderTest : IDisposable
{
    private IObjectNeedingDisposal _foo;

    public IObjectNeedingDisposal Foo
    {
        get { return _foo ?? (_foo = new ObjectNeedingDisposal()); }
        set { _foo = value; }
    }

    public void MethodUsingDisposable()
    {
        Foo.DoStuff();
    }

    public void Dispose()
    {
        Foo.Dispose();
    }
}

然后,在您的测试中,您可以创建对象的实例并传入类的模拟版本,但在实际实现中,您可以不设置它,它会默认为您想要的类型的实例

【讨论】:

  • 是的,我也考虑过这种方法,但我真的不喜欢仅仅为了单元测试的目的向类添加公共的东西。它看起来很笨重,而且它也是无法重用的代码。我知道我想做的事情可以通过反射来完成——我希望有人已经将它写在一个很好的可重用模块中以节省我的工作量:-)。
  • 我同意以这种方式使用属性并不完美,我更喜欢在构造函数中传递它们。我知道您可以使用反射来获取私有字段 - stackoverflow.com/questions/95910/…,但即便如此,您也会错过该事件,并且在调用了 Disposed 方法之后无法判断。
猜你喜欢
  • 2022-11-26
  • 2012-01-08
  • 1970-01-01
  • 2019-09-30
  • 2012-01-13
  • 2015-12-06
  • 2019-11-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多