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

4 回答 4

5

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

你能做的最好的事情是:

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

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


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

于 2012-06-24T01:41:54.680 回答
5

旧问题的新答案;我在处理完全相同的问题时发现了您的问题:我想编写一个单元测试,以验证如果 WeakReference 的所指对象变为空,我的测试类是否会执行非常具体的操作。

我首先编写了一个简单的测试用例,将所指对象设置为空;然后调用System.gc();并且有趣的是:至少在我的日食中,这对于我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 可能会做你希望它做的事情要好得多。

于 2016-04-28T14:29:19.957 回答
0

今天遇到一个类似的问题。

TLDR 解决方案:扩展 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
    }
}

然后而不是使用everyor when,只需使用mockWeakRef.setMockValue($x)

于 2021-07-12T16:47:20.940 回答
0

我想背负 GhostCat 向 Monica C 致敬的回答,该回答说要使用模拟。这当然是一条路线,但是在实现这一点时,我注意到它本身实际上有一个clear()功能WeakReference。因此,您可以创建一个工厂实例并简单地自己清除所指对象,而不是创建模拟的繁重工作。我使用的方法是用 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's 和 Bar's)。

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()
    }
}
于 2020-04-29T18:07:56.180 回答