Тестване на WeakReference

Какъв е правилният подход за тестване на слаба препратка в 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.


person John Ericksen    schedule 24.06.2012    source източник


Отговори (4)


Няма 100% бомбоустойчив начин за тестване на код, който използва референтните типове. Поведението на референтните обекти зависи от това кога се изпълнява GC и няма 100% надежден начин за принудително изпълнение на GC.

Най-доброто, което можете да направите е:

  • проверете дали сте задали правилните JVM опции, когато изпълнявате тестовете, и
  • напишете своя тест, така че да не се провали в случай, че System.gc() е без операция ИЛИ бъдете готови да деактивирате или пропуснете теста, или игнорирайте неуспеха на теста.

(Трябва да можете да откриете, че System.gc() се игнорира, като погледнете колко памет се използва преди и след повикването; напр. като извикате Runtime.totalMemory())


Всъщност има друго "решение". Нека вашият модулен тест генерира огромно количество боклук ... достатъчно, за да гарантира, че ще задействате събиране на боклук. (Не е добра идея, IMO.)

person Stephen C    schedule 24.06.2012

Нов отговор на стар въпрос; Открих въпроса ви, тъй като се занимавам с абсолютно същия проблем: искам да напиша единичен тест, за да проверя дали моят тестван клас прави нещо много специфично, ако референтът на WeakReference стане нулев.

Първо написах прост тестов случай, който ще зададе референта на нула; за да извикате след това System.gc(); и достатъчно интересно: поне в рамките на моето затъмнение, това беше „достатъчно добро“ за моето weakRefernce.get() да върне нула.

Но кой знае дали това ще работи за всички бъдещи среди, които ще изпълняват този модулен тест за идните години.

И така, след като помислих още малко:

@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 може би прави това, което се надявате да прави.

person GhostCat    schedule 28.04.2016
comment
Подигравката е най-добра тук, тъй като GC не е напълно детерминистичен, нито контролируем. - person kerner1000; 26.07.2017
comment
Много правилно - добавих още един параграф, за да стане по-ясно. За протокола: има ли нещо друго, което бих могъл да направя, за да направя това гласуване за достойно във вашите очи? - person GhostCat; 26.07.2017
comment
Мисля, че вече е съвсем ясно. Трябваше да създам друг конструктор за моя тип, който искам да тествам, за да осигуря подиграван WeakReference (конструктор основно за тестване на чист код ли е? ;)). Може би трябва да се отбележи, че подиграването на WeakReference е най-доброто за възпроизводими тестове, но по-малко реалистично, което означава, че ако искате изрично да тествате ненадеждно GC поведение, това може би не е най-добрият подход. (благодаря за гласуването за ;)) - person kerner1000; 26.07.2017
comment
Добре. Можете да използвате друг конструктор (който бих направил защитен от пакет); или когато използвате Mockito, има анотацията @InjectMocks (която аз лично не използвам, тъй като се проваля тихо - така че никога не знаете дали вашите макети наистина са били инжектирани). - person GhostCat; 26.07.2017

Искам да се откажа от 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)
                }
            }
        }
    }
}

За нашите тестове след това можем да внедрим 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'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()
    }
}
person CodyEngel    schedule 29.04.2020

Днес попаднах на подобен проблем.

TLDR решение: Разширете WeakReference и заменете .get()


Опитвах се да използвам mockk, за да се подигравам на WeakReference<*> по този начин:

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

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

След това обаче получавам a

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)

person Jeff Padgett    schedule 12.07.2021