Jak mogę poczekać na test JUnit?

95

Mam test JUnit, który chcę synchronicznie odczekać przez pewien okres czasu. Mój test JUnit wygląda następująco:

@Test
public void testExipres(){
    SomeCacheObject sco = new SomeCacheObject();
    sco.putWithExipration("foo", 1000);
    // WAIT FOR 2 SECONDS
    assertNull(sco.getIfNotExipred("foo"));
}

Próbowałem Thread.currentThread().wait(), ale zgłasza wyjątek IllegalMonitorStateException (zgodnie z oczekiwaniami).

Czy jest w tym jakaś sztuczka, czy potrzebuję innego monitora?

Kylar
źródło

Odpowiedzi:

119

A co powiesz Thread.sleep(2000);? :)

Muel
źródło
15
Jeśli używasz narzędzi do analizy kodu, takich jak sonarqube, będą narzekać, Thread.sleepmówiąc, że używanie Thread.sleep w teście jest ogólnie złym pomysłem. Tworzy kruche testy, które mogą zakończyć się niepowodzeniem w nieprzewidywalny sposób, w zależności od środowiska („Pasuje na moim komputerze”) lub obciążenia. Nie polegaj na synchronizacji (używaj makiet) ani nie używaj bibliotek, takich jak Awaitility, do testowania asynchronicznego.
FuryFart
4
Tę odpowiedź należy usunąć i uznać za szkodliwą. Znacznie lepszą odpowiedzią poniżej jest stackoverflow.com/a/35163873/1229735
yiati
73

W większości przypadków funkcja Thread.sleep () może zadziałać, ale zwykle, gdy czekasz, w rzeczywistości czekasz na wystąpienie określonego warunku lub stanu. Thread.sleep () nie gwarantuje, że wszystko, na co czekasz, faktycznie się wydarzyło.

Jeśli na przykład czekasz na żądanie odpoczynku, może ono zwykle powraca po 5 sekundach, ale jeśli ustawisz sen na 5 sekund w dniu, w którym prośba wróci za 10 sekund, test zakończy się niepowodzeniem.

Aby temu zaradzić, JayWay ma świetne narzędzie o nazwie Awatility, które jest idealne do zapewnienia, że ​​wystąpi określony stan, zanim przejdziesz dalej.

Ma również ładny, płynny interfejs API

await().until(() -> 
{
    return yourConditionIsMet();
});  

https://github.com/jayway/awaitility

Ben Glasser
źródło
Nie mogłem poczekać na kompilację w Android Studio.
IgorGanapolsky
Czy dodałeś tę linię do swojego pliku gradle: kompilacja „org.awaitility: awaitility: 3.0.0”?
Samoht
Nie. dodałbyś to w przypadku testowym, w którym chcesz poczekać na określony stan
Ben Glasser,
16

W przypadku, gdy Twój statyczny analizator kodu (taki jak SonarQube) narzeka, ale nie możesz wymyślić innego sposobu, zamiast snu, możesz spróbować z hackiem typu: Awaitility.await().pollDelay(Durations.ONE_SECOND).until(() -> true); Jest koncepcyjnie niepoprawny, ale jest taki sam jak Thread.sleep(1000).

Najlepszym sposobem, oczywiście, jest przekazanie wywołania, z odpowiednim stanem, a nie takim true, który mam.

https://github.com/awaitility/awaitility

rastaman
źródło
@Jitendra: Zauważ, że Awaitility miał Durationklasę, ale od wersji 4 zmienili jej nazwę na Durations.
Jacob van Lingen
Jedno zastrzeżenie, jeśli chcesz, aby opóźnienie sondy wynosiło 10 sekund lub więcej, pamiętaj, aby zwiększyć limit czasu do więcej niż 10 sekund, ponieważ jest to domyślny limit czasu, a funkcja oczekiwania będzie narzekać, że limit czasu jest krótszy niż opóźnienie odpytywania.
skup się
15

Możesz użyć biblioteki java.util.concurrent.TimeUnit, która wewnętrznie używa Thread.sleep. Składnia powinna wyglądać następująco:

@Test
public void testExipres(){
    SomeCacheObject sco = new SomeCacheObject();
    sco.putWithExipration("foo", 1000);

    TimeUnit.MINUTES.sleep(2);

    assertNull(sco.getIfNotExipred("foo"));
}

Ta biblioteka zapewnia bardziej przejrzystą interpretację jednostek czasu. Możesz użyć „GODZIN” / „MINUT” / „SEKUND”.

Akshay Pagar
źródło
Jeśli rozpocznę wątek roboczy z poziomu testu, czy sleep()wpłynie to na wątek roboczy?
Anthony Kong
6

Możesz również użyć CountDownLatchobiektu, jak wyjaśniono tutaj .

Pierre Voisin
źródło
0

Jest ogólny problem: trudno jest kpić z czasu. Ponadto umieszczanie długo działającego / oczekującego kodu w teście jednostkowym jest naprawdę złą praktyką.

Tak więc, aby umożliwić testowanie interfejsu API planowania, użyłem interfejsu z rzeczywistą i pozorowaną implementacją, taką jak ta:

public interface Clock {

    public long getCurrentMillis();

    public void sleep(long millis) throws InterruptedException;

}

public static class SystemClock implements Clock {

    @Override
    public long getCurrentMillis() {
        return System.currentTimeMillis();
    }

    @Override
    public void sleep(long millis) throws InterruptedException {
        Thread.sleep(millis);
    }

}

public static class MockClock implements Clock {

    private final AtomicLong currentTime = new AtomicLong(0);


    public MockClock() {
        this(System.currentTimeMillis());
    }

    public MockClock(long currentTime) {
        this.currentTime.set(currentTime);
    }


    @Override
    public long getCurrentMillis() {
        return currentTime.addAndGet(5);
    }

    @Override
    public void sleep(long millis) {
        currentTime.addAndGet(millis);
    }

}

Dzięki temu możesz naśladować czas w swoim teście:

@Test
public void testExipres() {
    MockClock clock = new MockClock();
    SomeCacheObject sco = new SomeCacheObject();
    sco.putWithExipration("foo", 1000);
    clock.sleep(2000) // WAIT FOR 2 SECONDS
    assertNull(sco.getIfNotExpired("foo"));
}

Zaawansowana wielowątkowa makieta dla Clockjest oczywiście znacznie bardziej złożona, ale można to zrobić na przykład za pomocą ThreadLocalreferencji i dobrej strategii synchronizacji czasu.

Dávid Horváth
źródło
0

Jeśli generowanie opóźnienia w teście CountDownLatchjest absolutną koniecznością, to proste rozwiązanie. W swojej klasie testowej zadeklaruj:

private final CountDownLatch waiter = new CountDownLatch(1);

oraz w teście w razie potrzeby:

waiter.await(1000 * 1000, TimeUnit.NANOSECONDS); // 1ms

Może nie trzeba mówić, ale pamiętaj, że powinieneś skracać czas oczekiwania i nie kumulować oczekiwania do zbyt wielu miejsc.

pirho
źródło