【问题标题】:Testing WeakReference测试弱引用
【发布时间】:2012-06-24 01:21:06
【问题描述】:

在 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 上的行为可能不同。

【问题讨论】:

    标签: java unit-testing weak-references


    【解决方案1】:

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

    你能做的最好的就是:

    • 在运行测试时检查您是否设置了正确的 JVM 选项,并且
    • 编写您的测试,使其在System.gc() 是空操作时不会失败愿意禁用或跳过测试,或忽略测试失败。李>

    (通过查看调用前后使用了多少内存,您应该能够检测到 System.gc() 被忽略;例如,通过调用 Runtime.totalMemory()


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

    【讨论】:

      【解决方案2】:

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

      我首先编写了一个简单的测试用例,它将所指对象设置为空;然后拨打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 可能会做你希望它做的事情要好得多。

      【讨论】:

      • Mocking 是最好的,因为 GC 不是完全确定的,也不是可控的。
      • 非常正确-我添加了另一段以使其更清楚。郑重声明:我还能做些什么来让这个投票在你眼中有价值吗?
      • 我觉得已经很清楚了。我必须为我想要测试的类型创建另一个构造函数以提供模拟的 WeakReference(构造函数主要用于测试干净的代码吗?;))。也许值得注意的是,模拟 WeakReference 最适合可重现的测试,但不太“现实”,这意味着如果您想显式测试不可靠的 GC 行为,它可能不是最好的方法。 (感谢您的支持;))
      • 好吧。您可以使用另一个构造函数(我会将其设置为受保护的包);或者在使用 Mockito 时,有 @InjectMocks 注释(我个人不使用它,因为它会默默地失败 - 所以你永远不知道你的模拟是否真的被注入了)。
      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

        今天遇到了类似的问题。

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


        试图使用 mockk 来模拟 WeakReference&lt;*&gt; 像这样:

        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 不喜欢 mock 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)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-12-31
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多