Подтвердить что ты не робот

Действительно ли необходимо уничтожить объекты в методах JUnit teardown?

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

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 obj был аннулирован, а в Test1 он gc'ed. Но test1 obj не был аннулирован, и он получил gc'ed в Test2. Это говорит о том, что обнуление объектов не требуется.

4b9b3361

Ответ 1

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

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

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

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

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

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

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

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

Ответ 2

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

Ответ 3

Нет, это не обязательно.

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

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

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