在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上的行为可能不同。
答案 0 :(得分:5)
没有100%防弹方式来测试使用Reference类型的代码。 Reference对象的行为取决于GC运行的时间,并且没有100%可靠的方法来强制GC运行。
你能做的最好的事情是:
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