2010-09-06 27 views
11

Tengo una aplicación en la que hay muchas pérdidas de memoria. Por ejemplo, si abre una vista y la cierra 10 veces, mi consumo de memoria aumenta porque las vistas no se limpian por completo. Estas son mis pérdidas de memoria. Desde una perspectiva impulsada por la prueba, me gustaría escribir una prueba que pruebe mis fugas y (después de que solucioné la fuga) afirmando que lo arreglé. De esa forma mi código no se romperá más adelante. En resumen:Fugas de memoria de prueba unitaria

¿Hay alguna manera de afirmar que mi código no está perdiendo memoria de una prueba unitaria?

p. Ej. ¿Puedo hacer algo como esto?

objectsThatShouldNotBeThereCount = MemAssertion.GetObjects<MyView>().Count; 
Assert.AreEqual(0, objectsThatShouldNotBeThereCount); 

No me interesa perfilar. Yo uso Ants Profiler (que me gusta mucho) pero también me gustaría escribir pruebas para asegurarme de que las "fugas" no vuelvan

Estoy usando C#/Nunit pero estoy interesado en cualquiera que tenga una filosofía sobre esto ...

Respuesta

2

No necesita pruebas unitarias, necesita perfilador de memoria. Puede comenzar con CLR Profiler.

+4

Ya estoy usando un generador de perfiles pero me gustaría "anclar" mis resultados para que se cree fácilmente una nueva fuga para el mismo escenario. – Gluip

4

El consumo de memoria aumenta no es necesariamente una indicación de una pérdida de recursos, ya que la recolección de basura no es determinista y es posible que todavía no se haya iniciado. A pesar de que "suelta" los objetos, el CLR es libre de mantenerlos mientras considere que hay suficientes recursos disponibles en el sistema.

Si sabe que hacer de hecho, tienen una pérdida de recursos, es posible trabajar con objetos que tienen Cerrar explícita/eliminarlo como parte de su contrato (quería decir por "utilizar ..." construcciones). En ese caso, si tiene control sobre los tipos, puede marcar la eliminación en los objetos de su implementación Dispose, para verificar que de hecho se han eliminado, si puede vivir con la administración del ciclo de vida filtrándose en la interfaz del tipo.

Si hace esto último, es posible probar la unidad que tiene lugar la eliminación contractual. Lo he hecho en algunas ocasiones, usando una aplicación específica equivalente a IDisposable (extendiendo esa interfaz), agregando la opción para consultar si el objeto ha sido eliminado. Si implementa esa interfaz explícitamente en su tipo, no contaminará tanto su interfaz.

Si no tiene control sobre los tipos en cuestión, un generador de perfiles de memoria, como se sugiere en otra parte, es la herramienta que necesita. (Por ejemplo, dotTrace de Jetbrains.)

+0

Quiere decir que podría probar que mi Disposición se llama para objetos específicos. Eso sería un buen comienzo, aunque sería una prueba muy específica. – Gluip

+0

Es difícil poner la prueba de disposición contractual después del hecho. Esas pruebas pertenecen a las pruebas unitarias de la aplicación en sí. Otro método que he utilizado para identificar las infracciones de disposición contractual después del hecho es Systems.Diagnostics.Assert fail del destructor del tipo (¡se aplican salvedades!), Si el distintivo IsDisposed no se configuró. Esto le dice (en el momento de la recolección de basura) que sucede, pero no cómo. Sin embargo, si se combina con mantener una instantánea de StackTrace del tiempo de creación de instancias del objeto, puede encontrar quién hizo la instancia y retroceder al motivo por el que no se eliminó. – Cumbayah

0

Es posible que pueda enganchar en el profiling API pero parece que tendría que iniciar las pruebas de su unidad con el generador de perfiles habilitado.

¿Cómo se crean los objetos? Directamente o de alguna manera que se puede controlar. Si es controlable, devuelva versiones extendidas con finalizadores que registren que han sido eliminados. Entonces

GC.Collect(); 
GC.WaitForPendingFinalizers(); 
Assert.IsTrue(HasAllOfTypeXBeenFinalized()); 
+0

buena idea. Desafortunadamente, creo mis objetos directamente, así que no puedo envolverlos con funcionalidades adicionales solo para probar. – Gluip

0

¿Qué tal algo como:

long originalByteCount = GC.GetTotalMemory(true); 
SomeOperationThatMayLeakMemory(); 
long finalByteCount = GC.GetTotalMemory(true); 
Assert.AreEqual(originalByteCount, finalByteCount); 
+0

Acabo de probarlo, esto no funciona. Por lo que puedo decir, el rastro de nunit agrega ruido a eso, por lo que una operación que debería ser neutral repentinamente no lo es. – Johannes

1

A menudo se introducen pérdidas de memoria cuando se utilizan tipos administrados los recursos no administrados sin el debido cuidado.

Un ejemplo clásico de esto es el System.Threading.Timer que toma un método de devolución de llamada como parámetro. Como el temporizador utiliza en última instancia un recurso no gestionado, se introduce una nueva raíz de GC que solo se puede liberar llamando al método Dispose del temporizador.En este caso, su tipo también debería implementar IDisposable; de lo contrario, este objeto nunca se puede recolectar como basura (una fuga).

puede escribir una prueba unitaria para este escenario haciendo algo similar a esto:

 var instance = new MyType(); 

     // ... 
     // Use your instance in all the ways that may trigger creation of new GC roots 
     // ... 

     var weakRef = new WeakReference(instance); 

     instance.Dispose(); 
     instance = null; 

     GC.Collect(); 
     GC.WaitForPendingFinalizers(); 
     GC.Collect(); 
     Assert.IsFalse(weakRef.IsAlive); 
1

dotMemory Unit marco tiene capacidades de programación para comprobar la cantidad de ciertos objetos asignados, tráfico de memoria, realizar y comparar instantáneas de memoria.