在JUnit的teardown方法中,将对象设置为null是否真的必要?

14

我被一个类似的问题的答案吸引了。 我认为那个答案是错误的。 因此,我创建了一些测试代码。我的问题是,这段代码是否证明/反驳/不确定在tearDown方法中将成员变量赋值为空是有用的假设? 我使用JUnit4.8.1进行了测试。

JUnit为每个测试创建测试类的新实例。 每个实例都包含一个Object obj。 这个obj也作为静态WeakHashMap的键插入其中。 如果JUnit释放对测试实例的引用,则相关联的obj值将变得弱引用并因此适合垃圾回收。 测试试图强制进行垃圾回收。 WeakHashMap的大小将告诉我objs是否已被垃圾回收。 一些测试将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对象被设置为null并在Test1中被垃圾回收。但是Test1对象没有被设置为null,却在Test2中被垃圾回收。这表明将对象设置为null并不是必需的。

2个回答

24
JUnit 4.x风格的测试和测试套件与JUnit 3.x测试套件处理方式不同。
简而言之,在JUnit3-style测试中,您应该将字段设置为null,但在JUnit4-style测试中不需要。
使用JUnit 3.x样式测试,TestSuite包含对其他Test对象的引用(可以是TestCase对象或其他TestSuite对象)。如果您创建了一个包含许多测试的套件,则整个最外层套件的所有叶子TestCase对象都将有硬引用。如果您的某些TestCase对象在setUp()中分配占用大量内存的对象,并且对这些对象的引用存储在未在tearDown()中设置为null的字段中,则可能会出现内存问题。
换句话说,对于JUnit 3.x样式测试,要运行哪些测试的规范引用实际的TestCase对象。在测试运行期间,从TestCase对象可达的任何对象都将保留在内存中。
对于JUnit 4.x风格的测试,指定要运行哪些测试使用Description对象。 Description对象是一个值对象,指定要运行什么,但不指定如何运行它。测试由Runner对象运行,该对象获取测试或测试套件的Description并确定如何执行测试。甚至将测试状态通知测试侦听器也使用Description对象。
JUnit4测试用例的默认运行程序JUnit4仅在运行该测试的持续时间内保留对测试对象的引用。如果使用自定义运行程序(通过@RunWith注释),则该运行程序可能会长时间保留对测试的引用。
也许您想知道如果在JUnit4样式的Suite中包含JUnit3样式的测试类会发生什么?JUnit4将调用new TestSuite(Class),这将为每个测试方法创建单独的TestCase实例。运行程序将在整个测试运行的生命周期内保留对TestSuite的引用。
简而言之,如果你正在编写JUnit4风格的测试,不必担心在tearDown()中将测试用例的字段设置为null(当然,要释放资源)。如果您正在编写分配大对象并将这些对象存储在TestCase字段中的JUnit3样式测试的setUp(),请考虑将字段设置为null

1
这是最好的答案。在 JUnit 3.x 中,空值处理是必要的(以避免内存泄漏)。如果您使用 JUnit4.x 和默认运行程序,则不需要进行空值处理。如果您使用自定义运行程序,则可能需要进行空值处理。 - emory

0

不,这并非必要。

拆除方法适用于有明确关闭、终止、关闭、处理、断开连接、注销等生命周期对象。

即使您的引用在下一个测试用例中仍然存在,它们也将被您的安装方法覆盖,并变得无法引用,因此可以进行垃圾回收。

即使JUnit为每种方法创建了您的测试用例的新实例(似乎是这种情况),那些测试对象也不会被保留。根据一个快速实验,至少在测试通过时是这样的。因此,所有这些都将在需要时进行收集。


无论是JUnit3风格的测试还是JUnit4风格的测试,都会为执行的每个测试方法创建一个单独的对象,因此,除非引用存储在静态变量中(不建议这样做),否则引用将不会被下一个设置方法覆盖。 - NamshubWriter
@NamshubWriter 这是一个保证还是实现细节?如果是后者,那么它可能会改变(虽然不太可能)。无论如何,在这些情况下(根据实验,在JUnit 4.8.1中),如果测试通过,则不会保留这些实例。 - Chris Vest
这不是实现细节,而是文档化的行为。JUnit 这样做是为了更难让一个测试的结果影响另一个测试的行为。 - NamshubWriter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接