2010-09-07 14 views
12

Me intrigó el answer a una pregunta similar. Yo creo que es incorrecto Así que creé un código de prueba. Mi pregunta es, ¿este código comprueba/refuta/no concluye la hipótesis de que es útil anular variables miembro en métodos de desmontaje? Lo probé con JUnit4.8.1.¿Es realmente necesario anular objetos en métodos de desmontaje JUnit?

JUnit crea una nueva instancia de la clase de prueba para cada una de las 4 pruebas. Cada instancia contiene un Object obj. Este obj también se inserta como la clave de un WeakHashMap estático. Si y cuando JUnit libera sus referencias a una instancia de prueba, el valor obj asociado se referenciará débilmente y por lo tanto es elegible para gc. La prueba intenta forzar un gc. El tamaño de WeakHashMap me dirá si los objs están escritos o no. Algunas pruebas anularon la variable obj y otras no.

import org . junit . Before ; 
import org . junit . After ; 
import org . junit . Test ; 
import java . util . ArrayList ; 
import java . util . WeakHashMap ; 
import java . util . concurrent . atomic . AtomicInteger ; 
import static org . junit . Assert . * ; 

public class Memory 
{ 
    static AtomicInteger idx = new AtomicInteger (0) ; 

    static WeakHashMap < Object , Object > map = new WeakHashMap < Object , Object > () ; 

    int id ; 

    Object obj ; 

    boolean nullify ; 

    public Memory () 
    { 
    super () ; 
    } 

    @ Before 
    public void before () 
    { 
    id = idx . getAndIncrement () ; 
    obj = new Object () ; 
    map . put (obj , new Object ()) ; 
    System . out . println ("<BEFORE TEST " + id + ">") ; 
    } 

    void test (boolean n) 
    { 
    nullify = n ; 
    int before = map . size () ; 
    gc () ; 
    int after = map . size () ; 
    System . out . println ("BEFORE=" + before + "\tAFTER=" + after) ; 
    } 

    @ Test 
    public void test0 () 
    { 
    test (true) ; 
    } 

    @ Test 
    public void test1 () 
    { 
    test (false) ; 
    } 

    @ Test 
    public void test2 () 
    { 
    test (true) ; 
    } 

    @ Test 
    public void test3 () 
    { 
    test (false) ; 
    } 

    @ After 
    public void after () 
    { 
    if (nullify) 
     { 
     System . out . println ("Nullifying obj") ; 
     obj = null ; 
     } 
    System . out . println ("<AFTER TEST " + id + ">") ; 
    } 

    /** 
    * Try to force a gc when one is not really needed. 
    **/ 
    void gc () 
    { 
    ArrayList <Object> waste = new ArrayList <Object> () ; 
    System . gc () ; // only a suggestion but I'll try to force it 
    list : 
    while (true) // try to force a gc 
     { 
     try 
      { 
      waste . add (new Object ()) ; 
      } 
     catch (OutOfMemoryError cause) 
      { 
      // gc forced? should have been 
      waste = null ; 
      break list ; 
      } 
     } 
    System . gc () ; // only a suggestion but I tried to force it 
    } 
} 

Me corrió el código utilizando la interfaz de línea de comandos (utilizando la opción -Xmx128k para aumentar la recolección de basura) y obtuvo el siguiente resultado

.<BEFORE TEST 0> 
BEFORE=1 AFTER=1 
Nullifying obj 
<AFTER TEST 0> 
.<BEFORE TEST 1> 
BEFORE=2 AFTER=1 
<AFTER TEST 1> 
.<BEFORE TEST 2> 
BEFORE=2 AFTER=1 
Nullifying obj 
<AFTER TEST 2> 
.<BEFORE TEST 3> 
BEFORE=2 AFTER=1 
<AFTER TEST 3> 

El obj Test0 fue anulado y en Prueba1 es gc 'ed. Pero el obj Test1 no fue anulado y se convirtió en Test2. Esto sugiere que anular objetos no es necesario.

Respuesta

21

Las pruebas de estilo JUnit 4.x y las suites de prueba manejan esto de manera diferente que las suites de prueba JUnit 3.x.

En resumen, usted debe establecer campos en NULL en las pruebas de estilo JUnit3 pero que no es necesario que en las pruebas de estilo junit4.

Con pruebas de estilo JUnit 3.x, un TestSuite contiene referencias a otros objetos Test (que puede ser TestCase objetos u otras TestSuite objetos). Si crea un conjunto con muchas pruebas, entonces habrá referencias difíciles a todos los objetos de la hoja TestCase para la ejecución completa de la suite más externa. Si algunos de sus objetos TestCase asignan objetos en setUp() que ocupan mucha memoria y las referencias a esos objetos se almacenan en campos que no están configurados en null en tearDown(), entonces es posible que tenga un problema de memoria.

En otras palabras, para las pruebas de estilo JUnit 3.x, la especificación de las pruebas a ejecutar hace referencia a los objetos reales TestCase. Cualquier objeto accesible desde un objeto TestCase se mantendrá en la memoria durante la prueba.

Para las pruebas de estilo JUnit 4.x, la especificación de las pruebas que se ejecutarán utiliza los objetos Description. El objeto Description es un objeto de valor que especifica qué ejecutar, pero no cómo ejecutarlo. Las pruebas son ejecutadas por un objeto Runner que toma el Description de la prueba o suite y determina cómo ejecutar la prueba. Incluso la notificación del estado de la prueba al oyente de prueba utiliza los objetos Description.

El corredor predeterminado para los casos de prueba JUnit4, JUnit4, mantiene una referencia al objeto de prueba solo durante la ejecución de esa prueba. Si utiliza un corredor personalizado (a través de la anotación @RunWith), ese corredor puede o no mantener referencias a las pruebas durante períodos de tiempo más largos.

Quizás se esté preguntando qué sucede si incluye una clase de prueba estilo JUnit3 en un estilo JUnit4 Suite? JUnit4 llamará al new TestSuite(Class), que creará una instancia de TestCase por método de prueba por separado. El corredor mantendrá una referencia al TestSuite durante toda la vida de la ejecución de prueba.

En resumen, si está escribiendo pruebas de estilo JUnit4, no se preocupe si configura los campos de su caso de prueba en null en un derribo (por supuesto, recursos gratuitos). Si está escribiendo pruebas de estilo JUnit3 que asignan objetos grandes en setUp() y almacena esos objetos en campos del TestCase, considere configurar los campos en null.

+1

Esta es la mejor respuesta. La anulación es necesaria (para evitar pérdidas de memoria) en JUnit 3.x. Si usa JUnit4.x y la anulación predeterminada del corredor no es necesaria. Si usa un corredor personalizado, entonces puede necesitar anularlo. – emory

0

No es necesario, pero ayuda al recolector de basura cuando necesita saber qué variables se usan o no; la variable nula está bastante garantizada para ser un buen candidato para la recolección de basura.

+1

Pero en mi experimento, el coleccionista de gc siempre logró averiguar si anulé o no. – emory

+0

¿Alguna vez ha trabajado con diferentes recolectores de basura? Algunos pueden ser bastante buenos, pero hay algunos recolectores de basura que necesitan un poco de ayuda – Drahakar

+2

'Ayuda al recolector de basura'. No, no. El objeto completo se libera después de cada prueba. Anular las referencias del objeto no ayuda en el más delgado. – EJP

0

No, no es necesario.

Los métodos de corte son para objetos de ciclo de vida que le gusta estar explícitamente cerrados, terminados, cerrados, eliminados, desconectados, no registrados o lo que sea.

Incluso si sus referencias sobreviven al siguiente caso de prueba, se sobrescribirán con su método de configuración y se convertirán en referencias y, por lo tanto, elegibles para la recolección de basura.

Y el evento si JUnit crea una nueva instancia de su caso de prueba para cada método (que parece ser el caso), esos objetos de prueba no se mantienen. Al menos no si la prueba pasa, según un experimento rápido. Por lo tanto, la mayor parte se recogerá en el ocio de todos modos.

+0

Tanto las pruebas de estilo JUnit3 como las pruebas de estilo JUnit4 crean un objeto separado por método de prueba que se ejecuta, por lo tanto las referencias no se sobrescribirán con el siguiente método de configuración – NamshubWriter

+0

@NamshubWriter a menos que las referencias se almacenen en estática (no recomendado) una garantía o un detalle de implementación? Si esto último, entonces podría cambiar (aunque poco probable). De todos modos, estas instancias no se mantienen (en JUnit 4.8.1, según el experimento), al menos no si pasa la prueba. –

+0

@NamshubWriter Actualicé mi respuesta. Gracias por la información. –

Cuestiones relacionadas