Как написать unit test, чтобы определить, может ли объект быть собранным мусором?
В отношении моего предыдущего вопроса, мне нужно проверить, будет ли компонент, который будет создан экземпляром Castle Windsor, может быть собран из мусора после того, как мой код завершил его использование, Я попробовал предложение в ответах на предыдущий вопрос, но, похоже, он работает не так, как ожидалось, по крайней мере для моего кода. Поэтому я хотел бы написать unit test, который проверяет, может ли конкретный экземпляр объекта быть мусором, собранным после запуска моего кода.
Можно ли сделать это надежным способом?
ИЗМЕНИТЬ
В настоящее время у меня есть следующий тест, основанный на ответе Павла Стовелла, который преуспевает:
[TestMethod]
public void ReleaseTest()
{
WindsorContainer container = new WindsorContainer();
container.Kernel.ReleasePolicy = new NoTrackingReleasePolicy();
container.AddComponentWithLifestyle<ReleaseTester>(LifestyleType.Transient);
Assert.AreEqual(0, ReleaseTester.refCount);
var weakRef = new WeakReference(container.Resolve<ReleaseTester>());
Assert.AreEqual(1, ReleaseTester.refCount);
GC.Collect();
GC.WaitForPendingFinalizers();
Assert.AreEqual(0, ReleaseTester.refCount, "Component not released");
}
private class ReleaseTester
{
public static int refCount = 0;
public ReleaseTester()
{
refCount++;
}
~ReleaseTester()
{
refCount--;
}
}
Правильно ли я полагаю, что, основываясь на вышеприведенном тесте, я могу заключить, что Windsor не будет утечки памяти при использовании NoTrackingReleasePolicy?
Ответы
Ответ 1
Это то, что я обычно делаю:
[Test]
public void MyTest()
{
WeakReference reference;
new Action(() =>
{
var service = new Service();
// Do things with service that might cause a memory leak...
reference = new WeakReference(service, true);
})();
// Service should have gone out of scope about now,
// so the garbage collector can clean it up
GC.Collect();
GC.WaitForPendingFinalizers();
Assert.IsNull(reference.Target);
}
NB: Очень много раз, когда вы должны вызывать GC.Collect() в производственном приложении. Но тестирование на утечки - один из примеров того, где это уместно.
Ответ 2
Возможно, вы могли бы провести WeakReference, а затем проверить, чтобы он больше не был жив (т.е., IsAlive) после тесты завершены.
Ответ 3
Основываясь на на ответе Павла, я создал более многоразовый метод Assert. Поскольку string
копируются по значению, я добавил для них явную проверку. Они могут собираться сборщиком мусора.
public static void IsGarbageCollected<TObject>( ref TObject @object )
where TObject : class
{
Action<TObject> emptyAction = o => { };
IsGarbageCollected( ref @object, emptyAction );
}
public static void IsGarbageCollected<TObject>(
ref TObject @object,
Action<TObject> useObject )
where TObject : class
{
if ( typeof( TObject ) == typeof( string ) )
{
// Strings are copied by value, and don't leak anyhow.
return;
}
int generation = GC.GetGeneration( @object );
useObject( @object );
WeakReference reference = new WeakReference( @object, true );
@object = null;
// The object should have gone out of scope about now,
// so the garbage collector can clean it up.
GC.Collect( generation, GCCollectionMode.Forced );
GC.WaitForPendingFinalizers();
Assert.IsNull( reference.Target );
}
Следующие модульные тесты показывают, что функция работает в некоторых распространенных сценариях.
[TestMethod]
public void IsGarbageCollectedTest()
{
// Empty object without any references which are held.
object empty = new object();
AssertHelper.IsGarbageCollected( ref empty );
// Strings are copied by value, but are collectable!
string @string = "";
AssertHelper.IsGarbageCollected( ref @string );
// Keep reference around.
object hookedEvent = new object();
#pragma warning disable 168
object referenceCopy = hookedEvent;
#pragma warning restore 168
AssertHelper.ThrowsException<AssertFailedException>(
() => AssertHelper.IsGarbageCollected( ref hookedEvent ) );
GC.KeepAlive( referenceCopy );
// Still attached as event.
Publisher publisher = new Publisher();
Subscriber subscriber = new Subscriber( publisher );
AssertHelper.ThrowsException<AssertFailedException>(
() => AssertHelper.IsGarbageCollected( ref subscriber ) );
GC.KeepAlive( publisher );
}
Из-за различий при использовании конфигурации Release
(я предполагаю, что оптимизация компилятора) некоторые из этих модульных тестов потерпят неудачу, если не было вызвано GC.KeepAlive()
.
Полный исходный код (включая некоторые из используемых вспомогательных методов) можно найти в моей библиотеке.
Ответ 4
Используйте dotMemory Unit (бесплатно)
[TestMethod]
public void ReleaseTest()
{
// arrange
WindsorContainer container = new WindsorContainer();
container.Kernel.ReleasePolicy = new NoTrackingReleasePolicy();
container.AddComponentWithLifestyle<ReleaseTester>(LifestyleType.Transient);
var target = container.Resolve<ReleaseTester>()
// act
target = null;
// assert
dotMemory.Check(memory =>
Assert.AreEqual(
0,
memory.GetObjects(where => where.Type.Is<ReleaseTester>().ObjectsCount,
"Component not released");
}
Ответ 5
Это не ответ, однако вы можете попробовать запустить свой код в режимах Отладка и Release (для сравнения).
По моему опыту Отладка версия JIT-кода упрощена для отладки и, следовательно, может видеть, что ссылки остаются живыми дольше (я верю функции). Однако код JITed в Release режим может иметь объекты, готовые к сбору, быстро, как только он выходит из сферы действия, и если происходит сборка.
Также не отвечая на ваш вопрос::-)
Мне было бы интересно увидеть, как вы отлаживаете этот код с помощью Visual Studio в режиме Interop (Managed and Native), а затем разбиваетесь после отображения окна сообщения или чего-то еще. Затем вы можете открыть Debug- > Windows-Immediate, а затем напечатать
load sos
(Change to thread 0)
!dso
!do <object>
!gcroot <object> (and look for any roots)
(или вы можете использовать Windbg, как другие, опубликованные в предыдущих сообщениях)
Спасибо,
Аарон