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?
java
junit
thread-safety
Kylar
źródło
źródło
Thread.sleep
mó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.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
źródło
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 jakThread.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
źródło
Duration
klasę, ale od wersji 4 zmienili jej nazwę naDurations
.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”.
źródło
sleep()
wpłynie to na wątek roboczy?Możesz również użyć
CountDownLatch
obiektu, jak wyjaśniono tutaj .źródło
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
Clock
jest oczywiście znacznie bardziej złożona, ale można to zrobić na przykład za pomocąThreadLocal
referencji i dobrej strategii synchronizacji czasu.źródło
Jeśli generowanie opóźnienia w teście
CountDownLatch
jest 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.
źródło