如何对终结器进行unit testing?

我有以下类,它是IDisposable对象的装饰器(我省略了它添加的东西),它本身使用一个通用模式实现IDisposable

 public class DisposableDecorator : IDisposable { private readonly IDisposable _innerDisposable; public DisposableDecorator(IDisposable innerDisposable) { _innerDisposable = innerDisposable; } #region IDisposable Members public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion ~DisposableDecorator() { Dispose(false); } protected virtual void Dispose(bool disposing) { if (disposing) _innerDisposable.Dispose(); } } 

我可以轻松测试调用Dispose()时是否Dispose()innerDisposable

 [Test] public void Dispose__DisposesInnerDisposable() { var mockInnerDisposable = new Mock(); new DisposableDecorator(mockInnerDisposable.Object).Dispose(); mockInnerDisposable.Verify(x => x.Dispose()); } 

但是,如何编写测试以确保innerDisposable不会被终结器处理? 我想写这样的东西,但它失败了,大概是因为GC线程没有调用终结器:

 [Test] public void Finalizer__DoesNotDisposeInnerDisposable() { var mockInnerDisposable = new Mock(); new DisposableDecorator(mockInnerDisposable.Object); GC.Collect(); mockInnerDisposable.Verify(x => x.Dispose(), Times.Never()); } 

我可能会误解,但是:

 GC.WaitForPendingFinalizers(); 

可能会做的 – http://msdn.microsoft.com/en-us/library/system.gc.waitforpendingfinalizers.aspx

编写unit testing时,应始终尝试测试外部可见行为,而不是实现细节。 人们可能会争辩说,压制最终确定确实是在可见行为之外,但另一方面,你可能也不可能(也不应该)嘲笑这个关注收集者。

在您的案例中,您尝试确保的是遵循“最佳实践”或编码实践。 它应该通过为此目的而制作的工具来强制执行,例如FxCop 。

我使用Appdomain(参见下面的示例)。 类TemporaryFile在构造函数中创建临时文件,并在Dispose或finalizer~TemporaryFile()中删除它。

不幸的是, GC.WaitForPendingFinalizers() ; 不能帮我测试终结器。

  [Test] public void TestTemporaryFile_without_Dispose() { const string DOMAIN_NAME = "testDomain"; const string FILENAME_KEY = "fileName"; string testRoot = Directory.GetCurrentDirectory(); AppDomainSetup info = new AppDomainSetup { ApplicationBase = testRoot }; AppDomain testDomain = AppDomain.CreateDomain(DOMAIN_NAME, null, info); testDomain.DoCallBack(delegate { TemporaryFile temporaryFile = new TemporaryFile(); Assert.IsTrue(File.Exists(temporaryFile.FileName)); AppDomain.CurrentDomain.SetData(FILENAME_KEY, temporaryFile.FileName); }); string createdTemporaryFileName = (string)testDomain.GetData(FILENAME_KEY); Assert.IsTrue(File.Exists(createdTemporaryFileName)); AppDomain.Unload(testDomain); Assert.IsFalse(File.Exists(createdTemporaryFileName)); } 

测试finalization并不容易,但测试一个对象是否是垃圾收集的主题可能更容易。

这可以通过弱引用来完成。

在测试中,在调用GC.Collect()之前使局部变量超出范围是很重要的。 确保最简单的方法是function范围。

  class Stuff { ~Stuff() { } } WeakReference CreateWithWeakReference(Func factory) { return new WeakReference(factory()); } [Test] public void TestEverythingOutOfScopeIsReleased() { var tracked = new List(); var referer = new List(); tracked.Add(CreateWithWeakReference(() => { var stuff = new Stuff(); referer.Add(stuff); return stuff; })); // Run some code that is expected to release the references referer.Clear(); GC.Collect(); Assert.IsFalse(tracked.Any(o => o.IsAlive), "All objects should have been released"); } [Test] public void TestLocalVariableIsStillInScope() { var tracked = new List(); var referer = new List(); for (var i = 0; i < 10; i++) { var stuff = new Stuff(); tracked.Add(CreateWithWeakReference(() => { referer.Add(stuff); return stuff; })); } // Run some code that is expected to release the references referer.Clear(); GC.Collect(); // Following holds because of the stuff variable is still on stack! Assert.IsTrue(tracked.Count(o => o.IsAlive) == 1, "Should still have a reference to the last one from the for loop"); }