测试弱引用

9
什么是在Java中测试弱引用的适当方法?
我的初始想法是执行以下操作:
public class WeakReferenceTest {

    public class Target{
        private String value;    

        public Target(String value){
            this.value = value;
        }    
        public String toString(){
            return value;
        }
    }

    public class UsesWeakReference{    
        WeakReference<Target> reference;   

        public UsesWeakReference(Target test){
            reference = new WeakReference<Target>(test);
        }    
        public String call(){
            Target test = reference.get();
            if(test != null){
                return test.toString();
            }
            return "empty";
        }
    }

    @Test
    public void testWeakReference(){    
        Target target = new Target("42");

        UsesWeakReference usesWeakReference = new UsesWeakReference(target);    
        WeakReference<Target> triggerReference = new WeakReference<Target>(target);    
        assertEquals("42", usesWeakReference.call());

        target = null;    
        while(triggerReference.get() != null){
            System.gc();
        }

        assertEquals("empty", usesWeakReference.call());    
    }    
}

我对这种做法的保留意见是使用System.gc(),因为我了解它在不同的JVM上的行为可能有所不同。

4个回答

5

针对旧问题的新回答。我发现你的问题正好和我面临的问题一样:我想编写一个单元测试来验证我的被测试类在弱引用指向的对象变为空时,能够执行某些非常特定的操作。

我首先编写了一个简单的测试用例,将引用设置为null; 然后调用 System.gc(); 有趣的是:至少在我的Eclipse中,这对于我的 weakRefernce.get() 的返回值为null已经足够了。

但是谁知道这是否适用于未来几年运行此单元测试的所有环境。

因此,经过更深入的思考:

@Test
public void testDeregisterOnNullReferentWithMock() {
    @SuppressWarnings("unchecked")
    WeakReference<Object> weakReference = EasyMock.createStrictMock(WeakReference.class);
    EasyMock.expect(weakReference.get()).andReturn(null);
    EasyMock.replay(weakReference);
    assertThat(weakReference.get(), nullValue());
    EasyMock.verify(weakReference);
}

这也很好用。

意思是:解决这个问题的通用答案是为对象创建WeakReference的工厂。因此,当您想测试生产代码时,可以向其提供模拟工厂;该工厂将进一步模拟WeakReference对象;现在您完全控制该弱引用对象的行为。

而“完全控制”要比假设GC可能会做你希望它做的事情好得多。


在这里最好使用模拟,因为垃圾回收并不完全具有确定性和可控性。 - kerner1000
非常正确 - 我添加了另一个段落以使其更清晰。为记录:在您的眼中,我还能做些什么来值得点赞? - GhostCat
我认为已经很清楚了。我不得不为我想要测试的类型创建另一个构造函数,以提供模拟的WeakReference(这是主要用于测试干净代码的构造函数吗?;))。也许值得注意的是,模拟WeakReference对于可重复的测试最好,但不太“现实”,这意味着如果您想显式测试不可靠的GC行为,则可能不是最佳方法。(感谢您的赞成;) - kerner1000
1
好的。您可以使用另一个构造函数(我会将其设置为包保护);或者在使用Mockito时,有@InjectMocks注释(我个人不使用它,因为它会静默失败 - 所以您永远不知道您的模拟是否真正被注入)。 - GhostCat

5
没有百分之百可靠的方法来测试使用引用类型的代码。引用对象的行为取决于GC何时运行,并且没有100%可靠的方法来强制运行GC。最好的方法是:
- 检查在运行测试时是否设置了正确的JVM选项,以及 - 编写测试使其不会因System.gc()无效而失败愿意禁用、跳过或忽略测试失败。
(您应该能够通过调用Runtime.totalMemory()来检测是否忽略了System.gc();例如,在调用前后检查内存使用情况。)
实际上,还有另一种“解决方案”。让您的单元测试生成大量垃圾...足以保证触发垃圾收集。(在我看来这不是一个好主意。)

0
我想借鉴GhostCat向Monica C的答案,他建议使用mocks。这当然是一种方法,但在实施过程中我注意到WeakReference本身实际上有一个clear()函数。所以,您可以创建一个工厂实例,并简单地清除引用对象,而不必费力地创建mocks。我使用的方法是用Kotlin编写的,希望语法不会让人感到太突兀,但这就是我的代码。
我们的工厂看起来像这样,您可以将invoke()函数视为构造函数,从功能上来说它就是构造函数,实际上它为我们节省了实现默认行为的类的工作。
interface WeakReferenceFactory {
    fun <T> create(referent: T): WeakReference<T>

    companion object {
        // Allows us to create a production ready instance with WeakReferenceFactory(), avoids having to implement a concrete instance.
        operator fun invoke(): WeakReferenceFactory {
            return object : WeakReferenceFactory {
                override fun <T> create(referent: T): WeakReference<T> {
                    return WeakReference(referent)
                }
            }
        }
    }
}

在我们的测试中,我们可以使用一个额外的clear()函数来实现工厂,这样做可以让我们在测试中保留正在使用的实例的引用,然后只需将其传递到工厂进行清除。
class WeakReferenceFactoryFake : WeakReferenceFactory {
    private val managedReferents = mutableListOf<WeakReference<*>>()

    fun <T> clear(referent: T) {
        managedReferents.filter { it.get() == referent }
            .forEach { it.clear() }
    }

    override fun <T> create(referent: T): WeakReference<T> {
        val weakReference = WeakReference(referent)
        managedReferents.add(weakReference)
        return weakReference
    }
}

然后在你的测试中,你会有类似这样的东西(对于Foo和Bar我很抱歉)。

class FooTest {
    private val fakeWeakReferenceFactory = WeakReferenceFactoryFake()

    private val subject: Foo = Foo(fakeWeakReferenceFactory)

    @Test
    fun `given foo, when bar is cleared, then bar should be null`() {
        val bar = Bar()
        foo.put(bar)

        fakeWeakReferenceFactory.clear(bar)

        assert(bar).isNull()
    }
}

0
今天遇到了类似的问题。
简短解决方案:扩展WeakReference并覆盖.get()方法。

我试图使用mockk来模拟WeakReference<*>,代码如下:

val weakRef = mockk<WeakReference<String>>()

@Test
fun testWeakRef() {
   every {weakRef.get()} returns "apple"
   // ... etc, etc
}

然而,接下来我得到了一个

Missing mocked calls inside every { ... } block: make sure the object inside the block is a mock

还不清楚为什么 mockk 不喜欢模拟 WeakReference。

所以,可以在您的测试目录中扩展该类

class MockWeakReference<T>(initialValue: T? = null) : WeakReference<T>(null) {

    private var mockValue = initialValue

    override fun get(): T? {
        return mockValue
    }

    fun setMockValue(value: T?) {
        mockValue = value
    }
}

然后,不再使用everywhen,只需使用mockWeakRef.setMockValue($x)


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