C# 如何对终结器进行单元测试?
我有一个类,它是C# 如何对终结器进行单元测试?,c#,.net,unit-testing,idisposable,finalizer,C#,.net,Unit Testing,Idisposable,Finalizer,我有一个类,它是IDisposable对象的装饰器(我省略了它添加的东西),它本身使用一个公共模式实现IDisposable: public class DisposableDecorator : IDisposable { private readonly IDisposable _innerDisposable; public DisposableDecorator(IDisposable innerDisposable) { _innerDispo
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()
时,我可以很容易地测试innerDisposable
是否被释放:
[Test]
public void Dispose__DisposesInnerDisposable()
{
var mockInnerDisposable = new Mock<IDisposable>();
new DisposableDecorator(mockInnerDisposable.Object).Dispose();
mockInnerDisposable.Verify(x => x.Dispose());
}
我可能有误解,但:
GC.WaitForPendingFinalizers();
可能会这样做-在编写单元测试时,您应该始终尝试测试外部可见的行为,而不是实现细节。有人可能会说,禁止终结确实是可见行为之外的行为,但另一方面,你可能无法(也不应该)嘲笑garabage收集器 在您的案例中,您试图确保遵循“最佳实践”或编码实践。它应该通过为此目的而设计的工具强制执行,例如。我使用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));
}
测试终结并不容易,但如果对象是要进行垃圾收集的对象,则可以更容易地进行测试 这可以通过弱引用来实现 在测试中,在调用GC.Collect()之前确保局部变量超出范围是很重要的。最简单的方法是确定函数作用域
class Stuff
{
~Stuff()
{
}
}
WeakReference CreateWithWeakReference<T>(Func<T> factory)
{
return new WeakReference(factory());
}
[Test]
public void TestEverythingOutOfScopeIsReleased()
{
var tracked = new List<WeakReference>();
var referer = new List<Stuff>();
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<WeakReference>();
var referer = new List<Stuff>();
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");
}
类内容
{
~Stuff()
{
}
}
WeakReference CreateWithWeakReference(函数工厂)
{
返回新的WeakReference(factory());
}
[测试]
public void Testerythingoutofscopeisreleased()
{
var tracked=新列表();
var referer=新列表();
tracked.Add(CreateWithWeakReference(()=>{var stuff=newstuff();referer.Add(stuff);returnstuff;}));
//运行一些希望释放引用的代码
referer.Clear();
GC.Collect();
Assert.IsFalse(tracked.Any(o=>o.IsAlive),“所有对象都应该被释放”);
}
[测试]
public void TestLocalVariableIsStillInScope()
{
var tracked=新列表();
var referer=新列表();
对于(变量i=0;i<10;i++)
{
var stuff=newstuff();
tracked.Add(CreateWithWeakReference(()=>{referer.Add(stuff);return stuff;}));
}
//运行一些希望释放引用的代码
referer.Clear();
GC.Collect();
//由于stuff变量仍在堆栈上,因此以下内容保持不变!
Assert.IsTrue(tracked.Count(o=>o.IsAlive)==1,“应该仍然有对for循环中最后一个的引用”);
}
是的。而且,单元测试永远不会得到100%的覆盖率。最终,你只需要相信你的代码会工作,如果你有能力,它应该会工作。我实际上有一个bug,因为我忘了检查Dispose()
中的disposing
标志,所以我想在修复它之前添加一个测试。我认为没有任何方法可以正确测试终结器,假设终结器可以在无限多的线程场景下运行。终结器可能最终在部分构造的对象上运行,因此通常只应在足够简单的类上使用,以允许通过检查验证所有场景。如果使用非托管资源的类过于复杂,不便于检查,则应将资源封装在其自身较小的类中,以便持有对持有资源的对象的引用的类不需要终结器。如此接近!这真的很聪明。它确实迫使终结器运行,并使我达到90%的目标。然而,在我的例子中,我需要能够使用一个伪造的垫片,并且在AppDomain中运行的代码看不到垫片。我也不能在DoCallback中创建垫片,因为在终结器运行之前它将超出范围。有人发现了吗?@SteveInCO你能把这个问题和你的案件来源一起公布吗?看到示例和搜索解决方案很有趣。@constructor:我最终没有得到我想要的断言。我的主要目标是获得代码覆盖率。(我总是追求100%的目标,这样我在编写新代码时一眼就能发现遗漏了什么)我想我可以使用临时文件来解决这个问题,但这需要在我测试的类中使用一些特定于测试的代码,这些代码看起来“脏”。也许如果我有时间,我会试着把一些东西放在一起演示,看看是否有人能想出更好的解决方案。您可以看到IDisposable的用法。这对我来说很好。在单元测试上下文中举个例子会很有帮助
class Stuff
{
~Stuff()
{
}
}
WeakReference CreateWithWeakReference<T>(Func<T> factory)
{
return new WeakReference(factory());
}
[Test]
public void TestEverythingOutOfScopeIsReleased()
{
var tracked = new List<WeakReference>();
var referer = new List<Stuff>();
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<WeakReference>();
var referer = new List<Stuff>();
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");
}