Ответ 1
Возможно, я ошибаюсь, но:
GC.WaitForPendingFinalizers();
Мог бы сделать трюк - http://msdn.microsoft.com/en-us/library/system.gc.waitforpendingfinalizers.aspx
У меня есть следующий класс, который является декоратором для объекта 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();
}
}
Я могу легко проверить, что innerDisposable
выставляется при вызове Dispose()
:
[Test]
public void Dispose__DisposesInnerDisposable()
{
var mockInnerDisposable = new Mock<IDisposable>();
new DisposableDecorator(mockInnerDisposable.Object).Dispose();
mockInnerDisposable.Verify(x => x.Dispose());
}
Но как мне написать тест, чтобы убедиться, что innerDisposable
не удаляется финализатором? Я хочу написать что-то вроде этого, но он терпит неудачу, по-видимому, потому, что финализатор не был вызван потоком GC:
[Test]
public void Finalizer__DoesNotDisposeInnerDisposable()
{
var mockInnerDisposable = new Mock<IDisposable>();
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
При написании модульных тестов вы всегда должны пытаться тестировать внешнее видимое поведение, а не детали реализации. Можно утверждать, что подавление финализации действительно является наружным видимым поведением, но, с другой стороны, возможно, вы не сможете (и не должны) издеваться над сборщиком габаритов.
То, что вы пытаетесь убедиться в вашем случае, заключается в том, что соблюдаются "лучшие практики" или практика кодирования. Он должен применяться с помощью инструмента, созданного для этой цели, например 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));
}
Непросто тестировать финализацию, но может быть проще протестировать, если объект является объектом сбора мусора.
Это можно сделать со слабыми ссылками.
В тесте важно, чтобы локальные переменные исчерпали область действия до вызова 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");
}