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がいつ実行されるかによって異なり、GCを強制的に実行する100%信頼できる方法はありません。

あなたができる最善のことは:

  • テストの実行時に適切なJVMオプションが設定されていることを確認し、
  • System.gc()何もしない場合、またはテストを無効にするかスキップする場合、またはテストの失敗を無視する場合に失敗しないように、テストを作成ます。

System.gc()(呼び出しの前後に使用されているメモリの量を調べることで、無視されていることを検出できるはずです。たとえば、を呼び出すことによってRuntime.totalMemory()


実際には、別の「解決策」があります。ユニットテストで大量のガベージを生成するようにします...ガベージコレクションをトリガーすることを保証するのに十分です。(良い考えではありません、IMO。)

于 2012-06-24T01:41:54.680 に答える
5

古い質問に対する新しい答え。まったく同じ問題を扱っているときにあなたの質問を見つけました。WeakReferenceの指示対象がnullになった場合に、テスト対象のクラスが非常に具体的なことを行うことを確認するために、単体テストを作成したいと思います。

私は最初に、指示対象をnullに設定する簡単なテストケースを作成しました。次に呼び出す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

GhostCatに敬意を表して、モックを使用するというMonicaCの回答に敬意を表したいと思います。これは確かに1つのルートですが、これを実装しているときに、実際にはそれ自体に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)
                }
            }
        }
    }
}

テストでは、追加の関数を使用してFactoryを実装でき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()
    }
}
于 2020-04-29T18:07:56.180 に答える
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
    }
}

everyまたはをwhen使用する代わりに、mockWeakRef.setMockValue($x)

于 2021-07-12T16:47:20.940 に答える