2013-05-17 17 views
13

Próbuję napisać test jednostkowy, który wymaga wielu wątków. Wydaje się jednak, że wątki po prostu przerywają częściowe wykonywanie. Rozważmy następujący kod:Nitka zachowuje się dziwnie w JUnit

public class Test { 
    @org.junit.Test 
    public void TestThreads() { 
     new Thread(new Runnable() { 
      public void run() { 
       for (int i = 1; i < 1000; i++) System.out.println(i); 
      } 
     }).start(); 
    } 
} 

Jeśli uruchomić ten test jednostki, to zazwyczaj zatrzymać wyświetlanie wyjścia gdzieś pomiędzy 140-180. Jeśli skonwertuję ten kod na zwykłą klasę i uruchomię go, działa dobrze. Czy ktoś ma pojęcie, czego tu brakuje?

Dzięki, - Andrew.

Odpowiedz

0

Prawdopodobnie kończy się wątek rodzica uruchamiający test. Jeśli możesz tego uniknąć, nie twórz nowego wątku w testach. Utworzyłem nową klasę implementującą działające, a następnie przetestowałem tę klasę, moje proste wywoływanie uruchomione na tym obiekcie. Nie powinieneś testować planowania wątków jako części twoich testów. Mam nadzieję, że zespół programistów języka Java ma to pokryte:

6

W funkcji TestThread JUnit nie ma możliwości poinformowania, że ​​utworzono nowy wątek. Odzyskuje wszelkie obiekty, które utworzył w funkcji, gdy tylko funkcja się zakończy (ostatni wiersz zostanie osiągnięty) i nie wie nic o wątku.

Z tego powodu widzisz Wynik z wątku, dopóki nie zostanie zabity. Jeśli chcesz poczekać na ukończenie wątku, możesz użyć Thread.join (zaraz po wywołaniu Thread.start()), aby poczekać na wynik lub użyć wait-notify dla jakiegoś obiektu.

również znaleźć na to pytanie: JUnit terminates child threads

+0

Powiedzieć, że JUnit "usuwa" obiekty jest trochę mylący, ponieważ w Javie nie ma możliwości wyraźnego usunięcia obiektu. –

+0

@Martin w prawo. ale JVM może odzyskać (GC), gdy obiekt nie jest już potrzebny. Po zakończeniu testów JUnit zabija wątki i zasadniczo całą maszynę JVM. – goblinjuice

+0

@Goblinjuice, co nie oznacza, że ​​JUnit w ogóle zabija instancję obiektu. Od momentu wyjścia JVM każdy bit pamięci, który zostanie użyty, zostanie zwolniony. –

22

Można użyć Thread.join() aby zapobiec test z wykańczania zanim nowy wątek zakończył zadanie:

@org.junit.Test 
public void TestThreads() throws InterruptedException { 
    Thread t = new Thread(new Runnable() { 
     public void run() { 
      for (int i = 1; i < 1000; i++) System.out.println(i); 
     } 
    }); 
    t.start(); 
    t.join(); 
} 

Normalnie JVM wygasa, gdy ostatni wątek nie będący demonem kończy się. Można się spodziewać, że po prostu wywołanie t.setDaemon(false) w wątku uniemożliwi maszynie JVM wyjście z tego zadania. Jednak junit będzie call System.exit(), gdy główny wątek został zakończony.

Jak zauważa Gus w komentarzach: "potrzebujesz join(), ponieważ start() nie blokuje".

Ma rację, że można również zadzwonić pod numer run() w tym minimalnym przykładzie. Zakładam jednak, że zaczynasz wątek, ponieważ chcesz, aby działał równolegle z innym wątkiem. Wywołanie run() na wątku jest oznaczane jako ewentualny błąd przez FindBugs - jeśli chcesz tylko zadzwonić pod numer run(), prawdopodobnie chcesz zaimplementować Runnable zamiast używać Thread.

+0

Sigh - uderz mnie do ciosu uczciwego i kwadratowego. –

+1

Warto wspomnieć w swoim rozwiązaniu, że potrzebujesz Join(), ponieważ Start() nie blokuje. IIRC, Możesz też po prostu użyć Run(), jeśli nie zamierzasz zrobić niczego jednocześnie. – Gus

+0

@Gus używając 'run' na instancji' Thread' jest złym pomysłem. Czemu?Ponieważ użycie 'run' nie czyni z niego nici, tylko zwykły obiekt. –

Powiązane problemy