Как я могу написать модульный тест, чтобы определить, можно ли собирать объект? - PullRequest
35 голосов
/ 23 февраля 2009

Что касается моего предыдущего вопроса , мне нужно проверить, может ли компонент, который будет создан Castle Windsor, собираться мусором после того, как мой код завершит его использование. Я попробовал предложение в ответах на предыдущий вопрос, но, похоже, оно не работает, как ожидалось, по крайней мере, для моего кода. Поэтому я хотел бы написать модульный тест, который проверяет, может ли конкретный экземпляр объекта быть собранным после выполнения части моего кода.

Это возможно сделать надежным способом?

EDIT

В настоящее время у меня есть следующий тест, основанный на ответе Пола Стовелла, который проходит успешно:

     [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?

Ответы [ 5 ]

80 голосов
/ 23 февраля 2009

Это то, что я обычно делаю:

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

Примечание: очень и очень редко вы должны вызывать GC.Collect () в производственном приложении. Но тестирование на утечки - один из примеров того, где это уместно.

4 голосов
/ 23 февраля 2009

Возможно, вы могли бы удерживать WeakReference на нем и затем проверять, чтобы он больше не существовал (то есть! IsAlive) после завершения тестов.

3 голосов
/ 22 марта 2012

Основываясь на ответе Пола , я создал более многократно используемый метод 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() не будет вызван.

Полный исходный код (включая некоторые используемые вспомогательные методы) можно найти в моей библиотеке .

2 голосов
/ 28 октября 2015

Использование dotMemory Unit framework (бесплатно)

[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");
}
1 голос
/ 24 февраля 2009

Это не ответ, однако вы можете попробовать запустить свой код в обоих режимах Debug и Release (для сравнения).

По моему опыту, версия JIT-кода Debug упрощена для отладки и, следовательно, может показывать, что ссылки дольше остаются живыми (я верю в область действия функции). иметь объекты, готовые для сбора, как только они выйдут из области видимости и если произойдет сбор.

Также не отвечая на ваш вопрос:: -)
Мне было бы интересно увидеть, как вы отлаживаете этот код с помощью Visual Studio в режиме взаимодействия (управляемый и собственный), а затем ломаетесь после отображения окна сообщения или чего-то еще. Затем вы можете открыть Debug-> Windows-Immediate и затем набрать

load sos
(Change to thread 0)
!dso
!do <object>
!gcroot <object> (and look for any roots)

(или вы можете использовать Windbg, как другие опубликовали в предыдущих сообщениях)

Спасибо, Аарон

...