2009-05-18 8 views
10

Czy ci się to podoba, czy nie, czasami musisz napisać testy dla klas, które wykorzystują wewnętrzne zegary.W jaki sposób oddziałujesz na klasy testowe, które korzystają z timerów wewnętrznie?

powiedzmy na przykład klasa, która wykonuje raporty o dostępności systemu i wywołuje zdarzenie, jeśli system został w dół zbyt długo

public class SystemAvailabilityMonitor { 
    public event Action SystemBecameUnavailable = delegate { }; 
    public event Action SystemBecameAvailable = delegate { }; 
    public void SystemUnavailable() { 
     //.. 
    } 
    public void SystemAvailable() { 
     //.. 
    } 
    public SystemAvailabilityMonitor(TimeSpan bufferBeforeRaisingEvent) { 
     //.. 
    } 
} 

mam parę sztuczek, które używam (zaksięguje je jako odpowiedź) ale zastanawiam się, co robią inni, ponieważ nie jestem w pełni usatysfakcjonowany z żadnego z moich podejść.

Odpowiedz

8

wyodrębnić timer z obiektu, który reaguje na alarm. W Javie możesz przekazać na przykład usługę ScheduledExecutorService. W testach jednostkowych przekazuję mu implementację, którą mogę kontrolować deterministycznie, taką jak jMock's DeterministicScheduler.

+0

Tak, to jakaś podwójna wysyłka (chyba?) byłoby idealnym podejściem, szkoda. NET sprawia, że ​​to jest kłopotliwe. Czasami uruchamiam swój własny zegar, ale zawsze czuję, że wprowadzam złożoność. –

+0

Brzmi bardziej jak wstrzyknięcie zależności niż podwójna wysyłka do mnie. –

+0

Niewielka terminologia, ale myślę, że słusznie nazywa się to podwójną wysyłką lub gościem.Jego DI jest pewny, ale bierzesz również obiekt i nakazujesz mu, aby zastosował swoją politykę aktualizacji do "tego". –

0

Sposoby, które zwykle zajmują się to albo

  1. Ustaw timer tick kiedykolwiek 100 milisekund i zrozumieć, że jej wątek będzie prawdopodobnie mój zostały włączone do tego czasu. Jest to niezręczne i daje nieco nieokreślone wyniki.
  2. Połączyć wydarzenie czasomierza z publicznym lub chronionym wewnętrznym zdarzeniem Tick(). Następnie z testu ustaw interwał czasowy na coś bardzo dużego i ręcznie wywołaj metodę Tick() z testu. To daje testy deterministyczne, ale jest kilka rzeczy, których po prostu nie można przetestować za pomocą tego podejścia.
0

I refactor te takie, że wartość temporalna jest parametrem metody, a następnie utworzyć inną metodę, która nie robi nic, ale przekazać prawidłowy parametr. W ten sposób całe rzeczywiste zachowanie jest izolowane i łatwe do testowania we wszystkich przypadkach dziwnych krawędzi, pozostawiając tylko bardzo trywialne wstawianie parametrów nieprzetestowane.

Jako niezwykle trywialny przykład, jeśli zacząłem z tym:

public long timeElapsedSinceJan012000() 
{ 
    Date now = new Date(); 
    Date jan2000 = new Date(2000, 1, 1); // I know...deprecated...bear with me 
    long difference = now - jan2000; 
    return difference; 
} 

chciałbym byłaby do tego, a jednostka przetestować drugą metodę:

public long timeElapsedSinceJan012000() 
{ 
    return calcDifference(new Date()); 
} 

public long calcDifference(Date d) { 
    Date jan2000 = new Date(2000, 1, 1); 
    long difference = d - jan2000; 
    return difference; 
} 
+1

Nie jestem pewien, czy to rozumiem, jak to się wiąże z zegarami? –

1

Brzmi jak należy drwić timer ale niestety ... po szybkim Google this other SO question z kilkoma odpowiedziami było najlepsze trafienie wyszukiwania. Ale wtedy zrozumiałem, że pytanie dotyczy klasy używającej timerów wewnętrznie, doh. W każdym razie, podczas programowania gier/silników - czasami przekazujesz timery jako parametry referencyjne do konstruktorów - co mogłoby sprawić, że znowu będzie to dla nich kpina? Ale potem znowu, jestem noob koder ^^

+0

Nie masz racji, idealnym sposobem na to jest przekazanie obiektu z timerem, jedynym problemem jest to, że łamie niektóre konwencje frameworków .NET i wydaje się być okropnym ciężarem dla niektórych rzeczy (teraz muszę skonfigurować mój kontener IoC za pomocą jeszcze inny obiekt na przykład) –

3

To właśnie używam. Znalazłem go w książce: Test Driven - Practical TDD and Acceptance TDD dla programistów Java autorstwa Lasse Koskela.

public interface TimeSource { 
    long millis(); 
} 


public class SystemTime { 

    private static TimeSource source = null; 

    private static final TimeSource DEFAULTSRC = 
     new TimeSource() { 
     public long millis() { 
      return System.currentTimeMillis(); 
     } 
    }; 


    private static TimeSource getTimeSource() { 
     TimeSource answer; 
     if (source == null) { 
      answer = DEFAULTSRC; 
     } else { 
      answer = source; 
     } 
     return answer; 
    } 

    public static void setTimeSource(final TimeSource timeSource) { 
     SystemTime.source = timeSource; 
    } 

    public static void reset() { 
     setTimeSource(null); 
    } 

    public static long asMillis() { 
     return getTimeSource().millis(); 
    } 

    public static Date asDate() { 
     return new Date(asMillis()); 
    } 

} 

Należy zauważyć, że domyślnym źródłem czasu, DEFAULTSRC, jest System.currentTimeMillis(). Jest zastępowany w testach jednostkowych; jednak normalne zachowanie to standardowy czas systemowy.

To gdzie służy on:

public class SimHengstler { 

    private long lastTime = 0; 

    public SimHengstler() { 
     lastTime = SystemTime.asMillis(); //System.currentTimeMillis(); 
    } 
} 

I tu jest test jednostki:

import com.company.timing.SystemTime; 
import com.company.timing.TimeSource; 

public class SimHengstlerTest { 
    @After 
    public void tearDown() { 
     SystemTime.reset(); 
    } 

    @Test 
    public final void testComputeAccel() { 
     // Setup 
     setStartTime(); 
     SimHengstler instance = new SimHengstler(); 
     setEndTime(1020L); 
    } 
    private void setStartTime() { 
     final long fakeStartTime = 1000L; 
     SystemTime.setTimeSource(new TimeSource() { 
      public long millis() { 
       return fakeStartTime; 
      } 
     }); 
    } 
    private void setEndTime(final long t) { 
     final long fakeEndTime = t; // 20 millisecond time difference 
     SystemTime.setTimeSource(new TimeSource() { 
      public long millis() { 
       return fakeEndTime; 
      } 
     }); 
    } 

W badanej jednostki, Wymieniłem źródła czasu tylko z numerem, który został ustawiony na 1000 milisekundy. To będzie służyć jako czas rozpoczęcia. Wywołując funkcję setEndTime(), wprowadzam 1020 milisekund na czas wykańczania. Dało mi to kontrolowaną różnicę czasu wynoszącą 20 milisekund.

W kodzie produkcyjnym nie ma kodu testowego, a jedynie normalny czas systemowy.

Upewnij się, że wywołasz reset po testach, aby powrócić do korzystania z metody czasu systemowego, a nie z ustawionego czasu.

0

Zdaję sobie sprawę, że jest to pytanie Java, ale może być interesujące, aby pokazać, jak to się robi w świecie Perla. Możesz po prostu zastąpić podstawowe funkcje czasu w swoich testach. :) To może wydawać się przerażające, ale oznacza to, że nie trzeba wstrzykiwać dużo dodatkowego kodu do kodu produkcyjnego, aby go przetestować. Jednym przykładem jest Test::MockTime. Zamrożenie czasu w teście sprawia, że ​​niektóre rzeczy są o wiele łatwiejsze. Podobnie jak te nieprzyjemne, nieatomowe testy porównawcze czasu, w których uruchamiasz coś w czasie X i do czasu, gdy sprawdzasz jego X + 1. W poniższym kodzie znajduje się przykład.

Nieco bardziej konwencjonalnie, ostatnio miałem klasę PHP do pobierania danych z zewnętrznej bazy danych. Chciałem, żeby to się działo co najwyżej raz na każde X sekundy. Aby go przetestować, ustawiłem zarówno ostatni czas aktualizacji, jak i przedział czasu aktualizacji jako atrybuty obiektu. Pierwotnie zrobiłem im stałe, więc ta zmiana do testowania również poprawiła kod. Następnie test mógł bawić się z tymi wartościami tak:

function testUpdateDelay() { 
    $thing = new Thing; 

    $this->assertTrue($thing->update, "update() runs the first time"); 

    $this->assertFalse($thing->update, "update() won't run immediately after"); 

    // Simulate being just before the update delay runs out 
    $just_before = time() - $thing->update_delay + 2; 
    $thing->update_ran_at = $just_before; 
    $this->assertFalse($thing->update, "update() won't run just before the update delay runs out"); 
    $this->assertEqual($thing->update_ran_at, $just_before, "update_ran_at unchanged"); 

    // Simulate being just after 
    $just_after = time() - $thing->update_delay - 2; 
    $thing->update_ran_at = $just_after; 
    $this->assertTrue($thing->update, "update() will run just after the update delay runs out"); 

    // assertAboutEqual() checks two numbers are within N of each other. 
    // where N here is 1. This is to avoid a clock tick between the update() and the 
    // check 
    $this->assertAboutEqual($thing->update_ran_at, time(), 1, "update_ran_at updated"); 
} 
4

Jeśli szukasz odpowiedzi na ten problem, może być zainteresowany w tym blogu: http://thorstenlorenz.blogspot.com/2009/07/mocking-timer.html

mnie to wyjaśnić sposób, aby zastąpić zwykłe zachowanie klasy System.Timers.Timer, aby uruchomić go na Start().

Oto krótka wersja:

class FireOnStartTimer : System.Timers.Timer 
{ 
public new event System.Timers.ElapsedEventHandler Elapsed; 

public new void Start() 
{ 
    this.Elapsed.Invoke(this, new EventArgs() as System.Timers.ElapsedEventArgs); 
} 
} 

Oczywiście wymaga to, aby być w stanie przejść timer do klasy badanego. Jeśli nie jest to możliwe, projekt klasy jest wadliwy, jeśli chodzi o testowalność, ponieważ nie obsługuje wtrysku zależności. Powinieneś zmienić jego projekt, jeśli możesz. W przeciwnym razie możesz mieć pecha i nie będziesz w stanie przetestować niczego na temat tej klasy, która wymaga wewnętrznego zegara.

Aby uzyskać dokładniejsze wyjaśnienie, odwiedź blog.

Powiązane problemy