2010-09-07 3 views
12

Я был заинтригован answer подобным вопросом. Я считаю, что это неверно. Поэтому я создал тестовый код. Мой вопрос заключается в том, что этот код доказывает/опровергает/отрицает гипотезу о том, что полезно аннулировать переменные-члены в методах слежения? Я тестировал его с помощью JUnit4.8.1.Действительно ли необходимо аннулировать объекты в методах JUnit teardown?

JUnit создает новый экземпляр тестового класса для каждого из 4 тестов. Каждый экземпляр содержит Object obj. Этот obj также вставлен как ключ статического WeakHashMap. Если и когда JUnit выпускает свои ссылки на тестовый экземпляр, связанное с ним значение obj будет слабо ссылаться и, следовательно, иметь право на gc. Тест пытается заставить gc. Размер WeakHashMap скажет мне, являются ли objs gc'ed. Некоторые тесты аннулировали переменную obj, а другие - нет.

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

Я побежал код с помощью интерфейса командной строки (использующего -Xmx128k возможность увеличить вывоз мусора) и получил следующий результат

.<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> 

Объект сос test0 был сведен и в Test1 это дс «ред. Но test1 obj не был аннулирован, и он получил gc'ed в Test2. Это говорит о том, что обнуление объектов не требуется.

ответ

21

JUnit 4.x тесты стиля и комплекты тестов обрабатывают это иначе, чем JUnit 3.x.

Короче говоря, вы должны установить поля NULL в тестах JUnit3 стиле но вам не нужно в тестах JUnit4 стиле.

С тестами в стиле JUnit 3.x, TestSuite содержит ссылки на другие Test объектов (которые могут быть TestCase объектов или другие объектами TestSuite). Если вы создадите пакет со множеством тестов, тогда будут жесткие ссылки на все листовые объекты TestCase для всего прогона самого внешнего набора. Если некоторые из объектов TestCase выделяют объекты в setUp(), которые занимают много памяти, а ссылки на эти объекты хранятся в полях, которые не установлены в null в tearDown(), тогда может возникнуть проблема с памятью.

Иными словами, для тестов стиля JUnit 3.x, спецификация которых для запуска ссылок содержит фактические объекты TestCase. Любые объекты, доступные из объекта TestCase, будут храниться в памяти во время тестового прогона.

Для тестов стиля JUnit 4.x, спецификация которых для запуска использует Description объектов. Объект Description - это объект значения, который указывает, что нужно запускать, но не как его запустить. Тесты выполняются объектом Runner, который принимает Description теста или набора и определяет, как выполнить тест. Даже уведомление о состоянии теста для тестового слушателя использует объекты Description.

По умолчанию бегун для тестовых примеров JUnit4, JUnit4, хранит ссылку на объект тестирования только на время выполнения этого теста. Если вы используете пользовательский бегун (через аннотацию @RunWith), этот бегун может или не может ссылаться на тесты в течение более длительных периодов времени.

Возможно, вам интересно, что произойдет, если вы включите класс тестирования JUnit3 в стиле JUnit4 Suite? JUnit4 вызовет new TestSuite(Class), который создаст отдельный экземпляр TestCase для каждого тестового метода. Бегун будет хранить ссылку на TestSuite на весь срок действия тестового прогона.

Короче говоря, если вы пишете тесты типа JUnit4, не беспокойтесь о том, чтобы установить поля вашего тестового случая на null в срыв (делайте, конечно, бесплатные ресурсы). Если вы пишете тесты типа JUnit3, которые выделяют большие объекты в setUp() и сохраняют эти объекты в полях TestCase, рассмотрите настройку полей на null.

+1

Это лучший ответ. Нулеумирование необходимо (чтобы избежать утечки памяти) в JUnit 3.x. Если вы используете JUnit4.x, а нумерация по умолчанию для бегуна не требуется. Если вы используете пользовательский бегун, вам может потребоваться аннулировать. – emory

0

Это действительно не обязательно, но это помогает сборщику мусора, когда ему нужно знать, какие переменные используются или нет; null переменная в значительной степени гарантирована, чтобы быть хорошим кандидатом на сбор мусора.

+1

Но в моем эксперименте коллекционеру gc всегда удавалось выяснить, сбрасывается ли я или нет. – emory

+0

Вы когда-нибудь работали с разными сборщиками мусора? Некоторые могут быть довольно хорошими, но есть некоторые сборщики мусора, которые нуждаются в небольшой помощи. – Drahakar

+2

«Это помогает сборщику мусора». Нет, нет. Весь объект выдается после каждого теста. Отбрасывание ссылок на объекты не помогает в самом слабом. – EJP

0

Нет необходимости в этом.

Методы сбрасывания предназначены для объектов, связанных с жизненным циклом, которые любят быть явно закрытыми, прекращенными, выключенными, расположенными, отключенными, незарегистрированными или любыми.

Даже если ваши ссылки сохранились до следующего тестового примера, они будут перезаписаны вашим методом настройки и не будут заменены и, следовательно, будут иметь право на сбор мусора.

И событие, если JUnit создает новый экземпляр вашего тестового примера для каждого метода (что, кажется, так), эти тестовые объекты не хранятся. По крайней мере, если тест проходит, в соответствии с быстрым экспериментом. Так или иначе, он будет собран на досуге.

+0

Оба теста стиля JUnit3 и тесты стиля JUnit4 создают отдельный объект для каждого тестового метода, который выполняется, поэтому, если ссылки не хранятся в статике (не рекомендуется), ссылки не будут перезаписаны следующим способом настройки. – NamshubWriter

+0

@NamshubWriter - это то, что гарантия или деталь реализации? Если последнее, то это может измениться (хотя и маловероятно). Несмотря на это, эти экземпляры не хранятся (в JUnit 4.8.1, согласно эксперименту) - по крайней мере, нет, если тест проходит. –

+0

@NamshubWriter Я обновил свой ответ. Спасибо за информацию. –