Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/24.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何对终结器进行单元测试?_C#_.net_Unit Testing_Idisposable_Finalizer - Fatal编程技术网

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