How do I unit test a finalizer?

自闭症网瘾萝莉.ら 提交于 2019-12-03 11:14:40

问题


I have the following class which is a decorator for an IDisposable object (I have omitted the stuff it adds) which itself implements IDisposable using a common pattern:

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

I can easily test that innerDisposable is disposed when Dispose() is called:

[Test]
public void Dispose__DisposesInnerDisposable()
{
    var mockInnerDisposable = new Mock<IDisposable>();

    new DisposableDecorator(mockInnerDisposable.Object).Dispose();

    mockInnerDisposable.Verify(x => x.Dispose());
}

But how do I write a test to make sure innerDisposable does not get disposed by the finalizer? I want to write something like this but it fails, presumably because the finalizer hasn't been called by the GC thread:

[Test]
public void Finalizer__DoesNotDisposeInnerDisposable()
{
    var mockInnerDisposable = new Mock<IDisposable>();

    new DisposableDecorator(mockInnerDisposable.Object);
    GC.Collect();

    mockInnerDisposable.Verify(x => x.Dispose(), Times.Never());
}

回答1:


I might be misunderstanding, but:

GC.WaitForPendingFinalizers();

Might do the trick - http://msdn.microsoft.com/en-us/library/system.gc.waitforpendingfinalizers.aspx




回答2:


When writing unit tests, you should always try to test outside visible behavior, not implementation details. One could argue that supressing finalization is indeed outside visible behavior, but on the other hand, there's probably no way you can (nor should you) mock out the garabage collector.

What you try to make sure in your case, is that a "best-practice" or a coding practice is followed. It should be enforced via a tool that is made for this purpose, such as FxCop.




回答3:


I use Appdomain (see sample below). Class TemporaryFile creates temporary file in constructor and delete's it in Dispose or in finalizer ~TemporaryFile().

Unfortunately, GC.WaitForPendingFinalizers(); doesn't help me to test finalizer.

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



回答4:


It's not easy to test finalization, but it can be easier to test if an object is a subject to garbage collection.

This can be done with a weak references.

In a test, it's important to for the local variables to run out of scope before calling GC.Collect(). The easiest way to make sure is a function scope.

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


来源:https://stackoverflow.com/questions/2895858/how-do-i-unit-test-a-finalizer

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!