36 votes

Comment puis-je écrire un test unitaire pour déterminer si un objet peut être collecté par les ordures ?

En ce qui concerne ma question précédente J'ai besoin de vérifier si un composant qui sera instancié par Castle Windsor peut être récupéré après que mon code ait fini de l'utiliser. J'ai essayé la suggestion dans les réponses de la question précédente, mais cela ne semble pas fonctionner comme prévu, du moins pour mon code. Je voudrais donc écrire un test unitaire qui vérifie si une instance d'objet spécifique peut être récupérée après l'exécution d'une partie de mon code.

Est-ce possible de le faire de manière fiable ?

EDIT

J'ai actuellement le test suivant basé sur la réponse de Paul Stovell, qui réussit :

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

Ai-je raison de supposer que, sur la base du test ci-dessus, je peux conclure que Windsor ne fera pas fuir la mémoire en utilisant la NoTrackingReleasePolicy ?

83voto

Paul Stovell Points 14880

C'est ce que je fais normalement :

[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 : Il y a très, très peu de cas où vous devriez appeler GC.Collect() dans une application de production. Mais le test des fuites est un exemple de cas où il est approprié.

4voto

denis phillips Points 7349

Vous pourriez peut-être organiser une Référence faible puis vérifier qu'il n'est plus en vie (c'est-à-dire !IsAlive) une fois les tests terminés.

4voto

Ed.ward Points 1056

Utilisez Unité de mémoire framework (c'est gratuit)

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

3voto

Steven Jeuris Points 4850

Basé sur sur la réponse de Paul j'ai créé une méthode Assert plus réutilisable. Puisque string sont copiés par valeur, j'ai ajouté une vérification explicite pour eux. Ils peuvent être collectés par le ramasseur d'ordures.

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

Les tests unitaires suivants montrent que la fonction fonctionne dans certains scénarios courants.

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

En raison des différences lors de l'utilisation de l Release (je suppose des optimisations du compilateur), certains de ces tests unitaires échouent si GC.KeepAlive() ne devaient pas être appelés.

Code source complet (y compris certaines des méthodes d'aide utilisées) peuvent être trouvés dans ma bibliothèque .

1voto

AaronBa Points 541

Ce n'est pas une réponse, mais vous pouvez essayer d'exécuter votre code dans les deux versions suivantes Déboguer y Communiqué de presse (à titre de comparaison).

D'après mon expérience, le Déboguer La version du code JIT est plus facile à déboguer et peut donc voir les références rester vivantes plus longtemps (je crois que la portée de la fonction). Communiqué de presse peut avoir les objets prêts pour la collecte rapidement une fois qu'il est hors du champ d'application et si une collecte se produit.

Je ne réponds pas non plus à votre question : :-)
Je serais intéressé de vous voir déboguer ce code à l'aide de Visual Studio en mode Interop (Managed et Native) puis de rompre après avoir affiché une boîte de message ou autre. Ensuite, vous pouvez ouvrir la fenêtre Debug->Windows-Immediate et ensuite taper

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

(ou vous pouvez utiliser Windbg comme d'autres l'ont fait dans des messages précédents).

Merci, Aaron

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X