测试WeakReference

时间:2012-06-24 01:21:07

标签: java unit-testing weak-references

在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 个答案:

答案 0 :(得分:5)

没有100%防弹方式来测试使用Reference类型的代码。 Reference对象的行为取决于GC运行的时间,并且没有100%可靠的方法来强制GC运行。

你能做的最好的事情是:

  • 检查运行测试时是否设置了正确的JVM选项,
  • 编写测试,以便在System.gc()为无操作愿意禁用或跳过测试或忽略测试失败的情况下不会失败。

(您应该能够通过查看呼叫前后使用的内存量来检测System.gc()被忽略;例如通​​过调用Runtime.totalMemory()


实际上,还有另一种“解决方案”。让您的单元测试产生大量垃圾......足以保证您将触发垃圾收集。 (不是一个好主意,IMO。)

答案 1 :(得分:4)

旧问题的新答案;我找到了你的问题,因为我正在处理完全相同的问题:我想编写一个单元测试,以验证我的测试类是否在WeakReference的指示对象变为null时做了非常具体的事情。

我首先编写了一个简单的测试用例,它将指示对象设置为null;然后拨打System.gc();而且有趣的是:至少在我的日食中,那是足够好的#34;我的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对象;现在你完全可以控制那个弱引用对象的行为。

&#34;完全控制&#34;比假设更好,GC可能会做你希望它做的事情。

答案 2 :(得分:0)

我想背负GhostCat致敬Monica C的回答,该回答说要使用模拟。当然,这是一条路线,但是在执行此操作时,我注意到实际上clear()本身上有一个WeakReference函数。因此,与其创建繁琐的模拟工作,不如创建一个工厂实例并自己清除引用即可。我使用的方法是用Kotlin写的,因此希望语法不会太令人讨厌,但这正是我所拥有的。

我们的Factory看起来像这样,您可以将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()函数来实现Factory,这样做可以使我们保留测试中使用的实例的引用,然后将其传递给要清除的工厂

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

答案 3 :(得分:0)

今天遇到了类似的问题。

TLDR 解决方案:扩展 WeakReference 并覆盖 .get()


试图用 mockk 来模拟 First 像这样:

WeakReference<*>

然而,我得到了一个

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

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

还不确定为什么 mockk 不喜欢模拟 WeakReference。

因此,只需在您的测试目录中扩展类

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

然后不要使用 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 } } every,只需使用 when