2016-05-16 78 views
3

我想为实现IDisposable的类编写单元测试。该课程有许多私人领域也实施IDisposable。在我的测试中,我想验证当我拨打Dispose()时,它在所有IDisposable字段上正确调用Dispose()。本质上,我想我的单元测试看起来像这样:如何使用反射进行单元测试Dispose()?

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

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

有人试过吗?

编辑 - 我不想将一次性用品注入被测试的课堂。

+0

''o.IsDisposed?在你的情况大概:''Assert.IsTrue(o.IsDispoed);'' –

+1

我建议使用依赖注入容器。管理一次性依赖关系只是一个好处。如果你的类创建了更多的需要跟踪和处理的依赖关系,那么它不依赖于抽象。有了DI,你的课程只需要依靠“IWhatever”。它不知道或不在乎该班是否可以丢弃。 –

回答

6

在代码中验证您正在查找的行为而不重构代码的唯一方法是使用代码编织工具Eg; Typemock Isolator,MsFakes等...

下面的代码片断显示,以验证使用MsFakes的行为方式:== true``

[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(); 
    } 
} 
+0

这样做的一个缺点是它不是通用的。就我所知,在运行期间没有自动生成填充的方法,因此您需要为该类中的每种一次性类型手动生成一个“ShimXxxxxx”。并且如果班级在以后使用新的一次性类型更新,单元测试不会检查它。这会消除单元测试值的一半,因为在添加新类型时它不能保护您。然而,有一个工作... –

+0

做'var fieldCount = typeof(ClassUnderTest).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Where(x => typeof(IDisposable).IsAssignableFrom(x.FieldType ))。计数(); Assert.AreEqual(KnownFieldCount,fieldCount,“已知字段的数量与类不匹配,根据需要用新的处理类更新单元测试”);'作为你的一个测试。如果有人用不同数量的一次性物品更新班级,这将导致测试失败,但是如果您同时删除和添加一个,则不会标记。 –

5

没有通用的方法来处理这个问题。 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(); 
    } 
+0

这是一个很好的答案。如果您正在处理类中的依赖关系,请编写测试来验证这一点。如果您将处置逻辑移到课堂之外,他们会中断,这是一件好事。 –

+0

另外,我并不介意在另一个接口(比如说ITrackedDisposable)中使用所有的Disposables,这些接口在他们被处置时会跟踪。 – user6118784

2

是你想测试你的IDisposable行为是否工作在给定类中,或者你要测试的无论是管理您的一次性类与否实际上是调用IDisposable?这是有效的,如果你不确定它是否被调用并且你想确定。 (我写测试了点。)

对于您可以创建一个简单的类是这样的:

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

然后,你可以断言,当你期望它已经布置Disposed是真实的。

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

+0

我想测试一下,当我调用ObjectUnderTest.Dispose()时,它会正确调用Dispose()的所有组合Disposables。我意识到我可以通过注入一次性物品来做到这一点,但我认为这是一个糟糕的设计,因为随着实施的变化,我的界面必须改变。看起来反射会是一种更简洁的方式来实现这一点,并且可以在任何IDisposable上重复使用。 – user6118784

+0

@ user6118784反映了什么?任何时候你想使用反射替换它“让所有的领域公开”,所以你的句子变成“看起来*使所有领域公开*将是一个更干净的方式来实现这一点,并可以重复使用一次又一次IDisposable的”。如何让这些字段公开解决你知道什么时候有人调用.Dispose()这些字段之一的问题?你必须拦截这个呼叫才能够检测到它,并且不需要进入真正奇特的东西,比如IL重写,你必须使用注入来完成它。 –

+0

回答你的问题:1)反映ObjectUnderTest的私人领域。 2)如果我公开了一次性字段,我的单元测试可以很容易地测试它们是否已被处理掉 - 我会将所有的一次性代码包装成类似于ITrackedDisposable(跟踪何时调用Dispose()的东西)来实现这个。 – user6118784

0

如果您不愿意在构造函数中使用依赖注入,因为这会迫使你改变你的接口,然后你可以通过属性注入你的依赖关系。如果没有注入任何东西,给你的财产一个默认值,这样,当你使用这个类时,你不必记得通过实际的实现

例如,

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(); 
    } 
} 

在您的测试,你可以再创建对象的实例,并通过在你的类的模拟版本,但在真正执行,你可以设置就可以了,它会默认为你想要的类型的实例

+0

是的,我也考虑过这个方法,但是我真的不喜欢仅仅为了单元测试的目的而将类添加到类中。它看起来笨重,而且它也是无法重用的代码。我知道我想要做的事情可以用反思来完成 - 我曾希望有人已经将它写在一个很好的可重用模块中以节省我的努力:-)。 – user6118784

+0

我同意以这种方式使用属性并不完美,我更喜欢将它们传递给构造函数。我知道你可以使用反射来获得私人领域 - http://stackoverflow.com/questions/95910/find-a-private-field-with-reflection但即使如此,你会错过了事件,并不能告诉在Disposed方法被调用的事实之后。 –