2012-07-15 22 views
29

Mam klasę, która ma prywatnego członka, który ma dla typu System.Windows.Forms.Timer. Istnieje również prywatna metoda, która jest wywoływana za każdym razem, gdy mój zegar się klika.Jednostka testująca klasę, która używa licznika czasu

  1. Czy warto przetestować tę metodę? (ponieważ jest prywatny)
  2. Jak mogę to przetestować? (Wiem, że moja klasa testowa może dziedziczyć klasę, którą chcę przetestować ...)
  3. Czy powinienem kpić z mojego timera? Ponieważ jeśli muszę przetestować klasę, która korzysta z wewnętrznego zegara, moje testy mogą zająć dużo czasu, prawda?

edit:

Faktycznie, metoda ma zależność od czasu, oto kod:

private void alertTick(object sender, EventArgs e) { 
    if (getRemainingTime().Seconds <= 0) { 
     Display.execute(Name, WarningState.Ending, null); 
     AlertTimer.Stop(); 
    } 
    else { 
     var warning = _warnings.First(x => x == getRemainingTime()); 

     if (warning.TotalSeconds > 0) 
      Display.execute(Name, WarningState.Running, warning); 
    } 
} 

Jak widać, gdy stoper jest uruchomiony, wywołuje Display.execute() z różnymi parametrami od momentu zakończenia (gdy pozostały czas jest równy 0). Czy to byłby problem projektowania?

+0

Jakie zachowanie chcesz zweryfikować? –

+2

Czy sama właściwa metoda ma jakąkolwiek zależność od czasu? Twój test jednostkowy powinien przetestować funkcjonalność metody; fakt, że jest wywoływany regularnie, nie powinien zmieniać faktu, że chcesz sprawdzić, czy metoda działa. Powiedziałbym, że zdecydowanie nie ma potrzeby kpić z timera, chyba że sukces metody zależy od czasu. Dostępność metody nie powinna mieć znaczenia, jeśli chodzi o testowanie; to funkcjonalna część twojego programu. – dash

+0

+1, ponieważ pisałem dokładnie to samo pytanie w przypadku testowania pól prywatnych, pobiłeś mnie na czas – HatSoft

Odpowiedz

29
  1. Nie testujesz metod (prywatnych lub publicznych) - sprawdzasz zachowanie swojej klasy.A jeśli nie sprawdziłeś jakiegoś zachowania, nie możesz powiedzieć, że zostało zaimplementowane. Istnieje kilka sposobów na wywołanie tego zachowania - publiczny interfejs twojej klasy lub pewne zdarzenie zależne. Nie jest również konieczne, aby wywoływanie zachowań zmieniało coś, co osiągnął interfejs publiczny, ale interakcje z zależnościami również mają znaczenie.
  2. Zobacz przykład poniżej - pokazuje, jak przetestować takie "ukryte" zachowanie.
  3. Zobacz przykład poniżej - pokazuje, jak podzielić obowiązki, wstrzyknąć zależności i wyśmiać je.

W rzeczywistości twoja klasa ma zbyt wiele obowiązków - jedna planuje jakieś zadanie, a druga - wykonuje pewne akcje. Spróbuj podzielić klasę na dwie osobne klasy za pomocą single responsibilities.

Więc, planowanie harmonogramów idzie :) API planującego może być tak:

public interface IScheduler 
{ 
    event EventHandler<SchedulerEventArgs> Alarm; 
    void Start(); 
    void Stop(); 
} 

Zapomnij o scheduler teraz. Wróć i zaimplementuj swoją drugą klasę, która wyświetli kilka ostrzeżeń. Chodźmy testową (z Min): realizacja

[Test] 
public void ShouldStopDisplayingWarningsWhenTimeIsOut() 
{ 
    Mock<IDisplay> display = new Mock<IDisplay>(); 
    Mock<IScheduler> scheduler = new Mock<IScheduler>();      

    Foo foo = new Foo("Bar", scheduler.Object, display.Object); 
    scheduler.Raise(s => s.Alarm += null, new SchedulerEventArgs(0)); 

    display.Verify(d => d.Execute("Bar", WarningState.Ending, null)); 
    scheduler.Verify(s => s.Stop()); 
} 

Zapis:

public class Foo 
{ 
    private readonly IScheduler _scheduler; 
    private readonly IDisplay _display; 
    private readonly string _name; 

    public Foo(string name, IScheduler scheduler, IDisplay display) 
    { 
     _name = name; 
     _display = display; 
     _scheduler = scheduler; 
     _scheduler.Alarm += Scheduler_Alarm; 
     _scheduler.Start(); 
    } 

    private void Scheduler_Alarm(object sender, SchedulerEventArgs e) 
    { 
     _display.Execute(_name, WarningState.Ending, null); 
     _scheduler.Stop(); 
    } 
} 

przechodzi test. Napisz następną:

[Test] 
public void ShouldNotStopDisplayingWarningsWhenTimeRemains() 
{ 
    Mock<IDisplay> display = new Mock<IDisplay>(MockBehavior.Strict); 
    Mock<IScheduler> scheduler = new Mock<IScheduler>(MockBehavior.Strict); 
    scheduler.Setup(s => s.Start()); 

    Foo foo = new Foo("Bar", scheduler.Object, display.Object); 
    scheduler.Raise(s => s.Alarm += null, new SchedulerEventArgs(1)); 
} 

Test nie powiódł się. Ach, trzeba warunku pozostały czas:

private void Scheduler_Alarm(object sender, SchedulerEventArgs e) 
{ 
    if (e.RemainingTime > 0) 
     return; 

    _display.Execute(_name, WarningState.Ending, null); 
    _scheduler.Stop(); 
} 

Można kontynuować pisanie testów dla klasy, która odpowiada za rozpatrzenie powiadomienia scheduler i wykonywanie niektórych ostrzeżenia na wyświetlaczu. Po zakończeniu możesz napisać implementację dla swojego interfejsu IScheduler. Nie ma znaczenia, w jaki sposób zaimplementujesz planowanie - za pośrednictwem System.Windows.Forms.Timer lub poprzez System.ThreadingTimer lub w inny sposób.

19

Czy warto przetestować tę metodę? (ponieważ jest prywatny)

Twoim celem jest ustalenie, czy twój kod działa, czy nie. Nawet jeśli jest to metoda prywatna, powinna generować dane wyjściowe, do których można uzyskać dostęp za pomocą publicznego interfejsu. Powinieneś zaprojektować swoją klasę w taki sposób, aby użytkownik wiedział, czy działa, czy nie.

Również podczas testowania jednostkowego połączenie zwrotne przypisane do wydarzenia, które upłynęło, jest osiągalne, jeśli można wyśmiać licznik czasu.

Jak mogę to przetestować? (Wiem, że moja klasa testowa może dziedziczyć klasę , którą chcę przetestować ...)

Możesz użyć tutaj klasy adaptera. Najpierw musisz zdefiniować abstrakcję, ponieważ klasa Timer jej nie oferuje.

public interface ITimer 
{ 
    void Start(); 
    void Stop(); 
    double Interval { get; set; } 
    event ElapsedEventHandler Elapsed; 
    //and other members you need 
} 

Następnie można zaimplementować ten interfejs w klasie adaptera, po prostu dziedzicząc z klasy Timer.

public class TimerAdaper : Timer, ITimer { } 

Powinieneś wstrzyknąć swoją abstrakcję w konstruktorze (lub jako własność), aby można było naśmiewać się z niej podczas testów.

public class MyClass 
{ 
    private readonly ITimer _timer; 

    public MyClass(ITimer timer) 
    { 
     _timer = timer 
    } 
} 

powinienem być szyderczy timer? Ponieważ jeśli muszę przetestować klasę, która używa wewnętrznego zegara, moje testy mogą zająć dużo czasu, prawda?

Oczywiście powinieneś kpić ze swojego timera. Twoje testy jednostkowe nie mogą zależeć od czasu systemowego. Powinieneś podnosić wydarzenia przez kpiny i zobaczyć, jak zachowuje się twój kod.

+0

proszę wyjaśnić, czy prywatne warto przetestować, a następnie jak je przetestować. – HatSoft

+3

@HatSoft Jeśli interfejs publiczny (prywatny lub publiczny) nie może zostać osiągnięty przez publiczny interfejs klasy, kod jest martwy (nie można go użyć). Dlatego zawsze powinniśmy być w stanie przetestować cały kod w klasie. –

+0

@HatSoft Nawet kod dołączony do zdarzenia, które upłynęło, jest prywatny, część, w której został uruchomiony powinien znajdować się w jakimś publicznym elemencie (może to być konstruktor). To sprawia, że ​​część prywatna jest dostępna. Bez kontekstu nie mogę powiedzieć, jak możesz go przetestować, ale powinieneś i możesz go przetestować. –

Powiązane problemy