真的有必要在 JUnit 拆解方法中取消对象吗?

2022-09-04 01:27:48

我对类似问题的答案很感兴趣。我认为这是不正确的。所以我创建了一些测试代码。我的问题是,这段代码是否证明/反驳/不确定了在拆解方法中消除成员变量是有用的假设?我用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没有被作废,它在Test2中得到了gc'ed。这表明不需要使对象无效。


答案 1

JUnit 4.x 样式测试和测试套件处理此问题的方式与 JUnit 3.x 测试套件不同。

简而言之,您应该在 JUnit3 样式的测试中将字段设置为 null,但在 JUnit4 样式的测试中不需要这样做

在 JUnit 3.x 样式测试中,a 包含对其他对象(可能是对象或其他对象)的引用。如果创建一个包含许多测试的套件,则在最外层套件的整个运行过程中,将硬引用所有叶对象。如果某些 TestCase 对象在 中分配了占用大量内存的对象,并且对这些对象的引用存储在 未设置为 in 的字段中,则可能存在内存问题。TestSuiteTestTestCaseTestSuiteTestCasesetUp()nulltearDown()

换句话说,对于 JUnit 3.x 样式测试,要运行的测试的规范引用了实际对象。在测试运行期间,可从对象访问的任何对象都将保留在内存中。TestCaseTestCase

对于 JUnit 4.x 样式测试,要运行的测试的规范使用 Description 对象。该对象是一个值对象,它指定要运行的内容,但指定如何运行它。测试由一个对象运行,该对象采用测试或套件并确定如何执行测试。甚至将测试状态通知给测试侦听器也会使用这些对象。DescriptionRunnerDescriptionDescription

JUnit4 测试用例的默认运行程序 JUnit4 仅在该测试运行期间保留对测试对象的引用。如果使用自定义运行器(通过注释),则该运行器可能会也可能不会在更长的时间内保留对测试的引用。@RunWith

也许您想知道如果在JUnit4样式的套件中包含JUnit3样式的测试类会发生什么?JUnit4 将调用,这将为每个测试方法创建一个单独的实例。运行器将在测试运行的整个生命周期内保留对 的引用。new TestSuite(Class)TestCaseTestSuite

简而言之,如果您正在编写JUnit4样式的测试,请不要担心将测试用例的字段设置为拆解(当然,要有免费资源)。如果您正在编写 JUnit3 样式的测试,这些测试在 中分配大型对象并将这些对象存储在 的字段中,请考虑将这些字段设置为 。nullsetUp()TestCasenull


答案 2

不,没有必要。

拆解方法适用于生命周期内的对象,这些对象需要显式关闭、终止、关闭、释放、断开连接、取消注册或其他方式。

即使您的引用在下一个测试用例中幸存下来,它们也会被您的设置方法覆盖,并且变得未被引用,因此有资格进行垃圾回收。

如果 JUnit 为每个方法(似乎就是这种情况)创建了测试用例的新实例,则不会保留这些测试对象。根据快速实验,如果测试通过,至少不会。因此,无论如何,它的很多都将在闲暇时收集。


推荐