Biblioteka do kontroli czasu w testach

0

Hej
Prawdopodobnie już istnieje coś takiego, ale w ramach wolnego czasu postanowiłem zabrać się za pisanie biblioteki dającą dodatkową abstrakcje w kodzie aplikacji która byłaby odpowiedzialna za czas. Po co? Chodzi mi o to, żeby móc w testach wykonać natychmiastowy 'skok' w czasie do przodu, ale żeby wszystkie zaplanowane akcje podczas tego 'przeskoku' zostały wykonane. Dodatkowo aby łatwo móc zmienić czas systemowy aplikacji, bez jakieś większe zabawy w mocki. Przykładowy kod:

    @Test
    public void hackIntoFutureTest(){
        Foo foo = new Foo(); // object with int value
        FooAdd runnable = new FooAdd(foo); // add one to object value
        
        TestTimeManager timeManager = new TestTimeManager();
        ScheduledFuture schedule1 = timeManager.schedule(runnable, 1, TimeUnit.SECONDS);
        ScheduledFuture schedule2 = timeManager.schedule(runnable, 2, TimeUnit.SECONDS);
        ScheduledFuture schedule3 = timeManager.schedule(runnable, 3, TimeUnit.SECONDS);
        ScheduledFuture schedule4 = timeManager.schedule(runnable, 5, TimeUnit.SECONDS);
        
        timeManager.hackIntoFuture(4,TimeUnit.SECONDS);

        assertEquals(3,foo.getA());
        assertTrue(schedule1.isDone());
        assertTrue(schedule2.isDone());
        assertTrue(schedule3.isDone());
        assertFalse(schedule4.isDone());

    }

// Sam test i nazwy są tymczasowe, na potrzeby pokazania idei

Wszystko opierało by się na (póki co) prostym interface

public interface TimeManager {

    LocalDateTime now();
    ScheduledFuture schedule(Runnable runnable, long l, TimeUnit timeUnit);

}
//Ponownie nazwy umowne

W logice aplikacji byłaby używana zwykła implementacja, która prawdopodobnie byłaby nakładką na javowe now i Scheduler, ale po przez ten interface, łatwo w testach podmienić na tę pokazaną

Czy w ogóle jest sens się za to zabierać? Jakieś ewentualne jeszcze pomysły odnośnie rzeczy która ta biblioteka mogłaby robić?

0

Rozumiem koncepcję, ale po co ten skok chcesz robić?

0

Nie jestem fanem testów jednostkowych, gdzie jednostką jest klasa. Dużo bardziej wole podejście BDD, ewentualnie traktowanie pakietu jako jednostkę. W jednym z moich projektów testuje takie podejdzie, gdzie jestem wstanie postawić cały system działający w pamięci i na nim wykonywać testy JUnitem. Testuje wtedy pojedyncze moduły (będące własnie pakietem javowym, z jakiąś publiczną klasą jako API dla reszty systemu), ale nie mockuje zależności do innych rzeczy tylko działam na 'żywym organizmie'

0

Nie wiem czy to odpowiada twoim potrzebom, ale ja polecam dostarczać do danej klasy Clock, a potem w testach wstrzykiwać go jakiego się chce

0

Tak, też tak robię, zwłaszcza z fixed clockiem. Bardziej chodzi mi o możliwość zasymulowania upływu czasu

0

Czy sam fakt potrzeby symulacji upływu czasu nie brzmi niepokojąco? Może da się zaprojektować system tak, by taka symulacja nie była jednak potrzebna?
Wydaje mi się, że "działanie na żywym organizmie" kończy Ci się w przypadku integracji z innymi systemami, gdzie jesteś np. konsumentem danych, które są produkowane poza Twoją kontrolą.

0

@danek IMO jak najbardziej ma sens. Robiłem różne prostackie toole w ramach projektów i wkurzałem się, że nie mogłem znaleźć choć pół gotowca. Clock po prostu nie wystarcza (dokładnie przez schedulery).

Wersja wypas to serwis czasu (bogatsze NTP) dostępny dla różnych aplikacji, tak żeby można było testować zależności czasowe miedzy mikroserwisami. Np. odpalanie batchy o danych godzinach.

Jest biblioteka avaitility, ale ona tylko pozwala czekać generalnie, nie przeskakiwać czas.

Ogólnie fajnie by było, ale temat dość ambitny.

0

@jarekr000000: chciałbym nad tym pracować jako bilbioteka opensource. Jakieś porady odnośnie tego? Na czym się skupić? Co by było potrzebne, jakie możliwości?

1

Testy dobre i dokumentacja. Mniej funkcji, ale lepiej udokumentowanych.

Clock serwis taki najprostszy, ale z dobrym api (oparte o Clock/localdatetime) - to już coś
Jakiś wrapper na schedulery. Coś w tym stylu jak piszesz.

Potem można rozbudowywać.

Podzieliłbym na dwie części:

  • lib dla aplikacji, czyli kontrolowalny czas
  • wsparcie do testów, dodatkowe asercje itp.
0

Mam wrażenie, że zapotrzebowanie na funkcjonalność wynika z faktu, że np. akcja do wykonania o określonej godzinie została pomyślana tak:

ActionResult doSomeAction()

i nie ma czegoś w rodzaju:

ActionResult doSomeAction(ActionRefData referenceData)

W efekcie zachodzi potrzeba symulowania upływu czasu, bo w logice akcji jest np. currentTime(), a można by zrobić referenceData.getReferenceDate()

Czy jakiś konkretny przykład z życia, że ten upływ (symulacja) czasu jest potrzebny?

2
yarel napisał(a):

Czy jakiś konkretny przykład z życia, że ten upływ (symulacja) czasu jest potrzebny?

  • Oczywiste : to oczywiście wszelkie testy, gdzie coś od currentTime zależy, to i dziś da się w miarę prosto robić... tylko nie ma standardu. Najbliższy jest taki sobie Clock.fixed() , gdzie nie można
    przesuwać czasu podczas testów , czyli nie da się assignTasks(); time.plusDays(1); assignTasks();
  • Watchdogi : Jeśli nie udało się wygenerować numeru klienta przez 5 minut, to ma być wysłany event.
    Nie chce czekać 5 minut w testach, zwłaszcza jak wiem, że sie nie udało, bo wyłaczyłem odpowiedni serwis :P,
  • Batche: Sprawdzanie działania wszelkich batchy, które mają się raz na dzień odpalić i coś tam przeliczyć, mogę sobie np. zasymulować cały miesiąc odpalania i sprawdzić czy stan kont np. się zgadza.

Z tym wszystkim jakoś sobie obecnie radze, i nie jest to dramat, ale wkurza mnie, że co system to muszę na nowo taką time infrastrukturę pod testy budować.

0

@jarekr000000:

Co masz na myśli jako 'wsparcie dla testów'?

0

Że z jednej strony potrzeba serwisu do wykorzystania w kodzie.

A z drugiej czegoś tylko for tests - faktyczny "mock czasu" :-), może jakieś matchery, asercje, nie wiem nawet.

0

Póki co myślałem nad jednym publicznym interfacem (ten z postu mojego) który miałby implementacje 'produkcyjną' oraz implementację testową, z dodatkowymi metodami pod testy (np. wspomniany przeskok czasu). Niestety mam za mało doświadczenia, żeby uznać czy ma to sens ;/

0
jarekr000000 napisał(a):
yarel napisał(a):

Czy jakiś konkretny przykład z życia, że ten upływ (symulacja) czasu jest potrzebny?

  • Oczywiste : to oczywiście wszelkie testy, gdzie coś od currentTime zależy, to i dziś da się w miarę prosto robić... tylko nie ma standardu. Najbliższy jest taki sobie Clock.fixed() , gdzie nie można
    przesuwać czasu podczas testów , czyli nie da się assignTasks(); time.plusDays(1); assignTasks();
  • Watchdogi : Jeśli nie udało się wygenerować numeru klienta przez 5 minut, to ma być wysłany event.
    Nie chce czekać 5 minut w testach, zwłaszcza jak wiem, że sie nie udało, bo wyłaczyłem odpowiedni serwis :P,
  • Batche: Sprawdzanie działania wszelkich batchy, które mają się raz na dzień odpalić i coś tam przeliczyć, mogę sobie np. zasymulować cały miesiąc odpalania i sprawdzić czy stan kont np. się zgadza.

Z tym wszystkim jakoś sobie obecnie radze, i nie jest to dramat, ale wkurza mnie, że co system to muszę na nowo taką time infrastrukturę pod testy budować.

Przy jakimś kodzie legacy, w którym jest jak jest, to faktycznie może mieć sens, o ile wysiłek na dostosowanie istniejącego kodu do takiej "infrastruktury do obsługi czasu" nie jest jakiś duży. Jeśli jest znaczny, albo system jest projektowany od zera, to osobiście nie widzę jakiejś wartości płynącej z takiego podejścia (w porównaniu do zaprojektowania akcji, tak by dało się ją wywołać z "odpowiednią datą" - w przypadku "od zera", jak i modyfikacji kodu "legacy/bieżącego") .

Dla mniej bardziej upierdliwe było radzenie sobie ze zbyt szybkim upływem czasu, w przypadku, gdy komponenty robiły timeouty (TimerTask uruchamiane za now()+timeout) po 30ms, a akurat chciałem coś debuggerem podejrzeć.

0
yarel napisał(a):

Przy jakimś kodzie legacy, w którym jest jak jest, to faktycznie może mieć sens, o ile wysiłek na dostosowanie istniejącego kodu do takiej "infrastruktury do obsługi czasu" nie jest jakiś duży. Jeśli jest znaczny, albo system jest projektowany od zera, to osobiście nie widzę jakiejś wartości płynącej z takiego podejścia (w porównaniu do zaprojektowania akcji, tak by dało się ją wywołać z "odpowiednią datą" - w przypadku "od zera", jak i modyfikacji kodu "legacy/bieżącego") .

Dla mnie zupełnie odwrotnie, widzę, że wrzucanie czegoś takiego w legacy nie ma wielkiego sensu (albo raczej nadziei), będzie kosztowne. Za to widzę sens dla nowych systemów.
Po to, żebym tych paru dodatkowych klas nie musiał pisać (to szczególnie dotyczy popychalnych schedulerów).

Dodatkowo widze sens w ustandaryzowaniu podejścia, nawet jeśli uprościmy sprawe li tylko do timeservice - what time is it?. Jak się uda ustandaryzować podejscie to może rzadziej będe widział te wszystkie new Date(), LocalDataTime.now() w kodzie.
(Btw. ciągle natrafiam na projekty, których testy wysypują się na moim kompie, bo klasyczne sprawdzenie, że obiekt b) został stworzony i zapisany w bazie po obiekcie a) i porównanie czasu w milisekundach nie działa (wychodzi różnica 0 :-), (k...a).

0

Dla mnie zupełnie odwrotnie, widzę, że wrzucanie czegoś takiego w legacy nie ma wielkiego sensu (albo raczej nadziei), będzie kosztowne. Za to widzę sens dla nowych systemów.
Po to, żebym tych paru dodatkowych klas nie musiał pisać (to szczególnie dotyczy popychalnych schedulerów).

Dodatkowo widze sens w ustandaryzowaniu podejścia, nawet jeśli uprościmy sprawe li tylko do timeservice - what time is it?. Jak się uda ustandaryzować podejscie to może rzadziej będe widział te wszystkie new Date(), LocalDataTime.now() w kodzie.
(Btw. ciągle natrafiam na projekty, których testy wysypują się na moim kompie, bo klasyczne sprawdzenie, że obiekt b) został stworzony i zapisany w bazie po obiekcie a) i porównanie czasu w milisekundach nie działa (wychodzi różnica 0 :-), (k...a).

Dla mnie scheduler to scheduler i ile można testować czy zachowuje się poprawnie. Zwłaszcza jak mamy jakiś zewnętrzny i po tym jak intuicja zawodzi, to czytamy dokumentację ;-)

Interesujące są przebiegi procesów i tu czekanie np. 2 tygodni na coś, jest słabe do testowania. Zamiast jednak popychać czas, czemu nie mieć "infrastruktury biznesowej wspierającej testy" i produkować odpowiednie zdarzenia czy wywoływać akcje bezpośrednio z odpowiednimi "parametrami czasowymi"?

test_scenario1:
	doActivity1()
	doActivity2(someRefDate)
	emitEvent(TwoWeeksPassed(now()+2weeks))
	...
test_scenario2:
	doActivity1()
	doActivity2(someRefDate)
	emitEvent(MailArrived(arrivalDate))
	...

Tak projektując "wsparcie" dla testowania możemy uniknąć zabawy ze schedulerami i popychaniem czasu. Wydaje mi się, że popychanie czasu jest dużo bardziej złożone i cięższe w utrzymaniu, np. jak ożenić wszelkie Thread.sleepy() z naszymi testami?

Myślę, że już samo rozważanie jak zaprojektować system, tak by dało się testować takie scenariusze jest dobrym podejściem, a te popychane schedulery jawią mi się jako doraźne rozwiązanie generujące bliżej nieokreślone błędy.

Z perspektywy programisty pewnie fajnie byłoby mieć standard, ale na jakim poziomie? np. w Javie mam, a w już C nie mam? W CORBIE jest TimeService, ale czy aplikacje nie byłyby za grube z taką warstwą a'la TimeService?

Co do porównań czasów to już widziałem podobne problemy i tłumaczenia (programistów z Reichu) 'zakładaliśmy, że w ciągu dnia nie może wystąpić więcej niż 1 zmiana' :D

0
yarel napisał(a):

Dla mnie scheduler to scheduler i ile można testować czy zachowuje się poprawnie. Zwłaszcza jak mamy jakiś zewnętrzny i po tym jak intuicja zawodzi, to czytamy dokumentację ;-)

Tu własnie chodzi o to, żeby nie testowac schedulerów tylko kod z nich korzystający.

Interesujące są przebiegi procesów i tu czekanie np. 2 tygodni na coś, jest słabe do testowania. Zamiast jednak popychać czas, czemu nie mieć "infrastruktury biznesowej wspierającej testy" i produkować odpowiednie zdarzenia czy wywoływać akcje bezpośrednio z odpowiednimi "parametrami czasowymi"?

Dokładnie chodzi właśnie o zrobienie infrastuktury wspierającej testy.

Trudno to może docenić, jeśli się nie pracowało z JS, ale tam w takim np. Jasmine jest dokładnie metoda tick, która popycha czas do przodu i umożliwia rewelacyjne testowanie wszelkich zależności czasowych.

0

btw jakby ktoś miał pomysł na nazwę to słucham :D

1 użytkowników online, w tym zalogowanych: 0, gości: 1