Najlepsza praktyka: zainicjować pola klasy JUnit w setUp () lub w deklaracji?


120

Czy powinienem zainicjować pola klas przy takiej deklaracji?

public class SomeTest extends TestCase
{
    private final List list = new ArrayList();

    public void testPopulateList()
    {
        // Add stuff to the list
        // Assert the list contains what I expect
    }
}

Lub w setUp () w ten sposób?

public class SomeTest extends TestCase
{
    private List list;

    @Override
    protected void setUp() throws Exception
    {
        super.setUp();
        this.list = new ArrayList();
    }

    public void testPopulateList()
    {
        // Add stuff to the list
        // Assert the list contains what I expect
    }
}

Zwykle używam pierwszego formularza, ponieważ jest bardziej zwięzły i pozwala mi używać końcowych pól. Jeśli nie muszę używać metody setUp () do konfiguracji, czy nadal powinienem jej używać i dlaczego?

Wyjaśnienie: JUnit utworzy instancję klasy testowej raz na metodę testową. Oznacza to, listże zostanie utworzony raz na test, niezależnie od tego, gdzie to zadeklaruję. Oznacza to również, że między testami nie ma zależności czasowych. Wygląda więc na to, że używanie setUp () nie ma żadnych zalet. Jednak JUnit FAQ zawiera wiele przykładów, które inicjują pustą kolekcję w setUp (), więc myślę, że musi być jakiś powód.


2
Uważaj, że odpowiedź różni się w JUnit 4 (inicjalizacja w deklaracji) i JUnit 3 (użyj setUp); to jest przyczyna zamieszania.
Nils von Barth

Odpowiedzi:


99

Jeśli zastanawiasz się konkretnie nad przykładami w JUnit FAQ, takimi jak podstawowy szablon testu , myślę, że najlepszą praktyką jest to, że testowana klasa powinna być utworzona w metodzie setUp (lub w metodzie testowej) .

Kiedy przykłady JUnit tworzą ArrayList w metodzie setUp, wszystkie przechodzą do testowania zachowania tej ArrayList, z przypadkami takimi jak testIndexOutOfBoundException, testEmptyCollection i tym podobne. Istnieje perspektywa kogoś, kto pisze zajęcia i upewnia się, że wszystko działa prawidłowo.

Prawdopodobnie powinieneś zrobić to samo podczas testowania własnych klas: stwórz swój obiekt w setUp lub w metodzie testowej, abyś mógł uzyskać rozsądny wynik, jeśli później go zepsujesz.

Z drugiej strony, jeśli używasz klasy kolekcji Java (lub innej klasy biblioteki, jeśli o to chodzi) w swoim kodzie testowym, prawdopodobnie nie dzieje się tak dlatego, że chcesz ją przetestować - to tylko część osprzętu testowego. W takim przypadku możesz spokojnie założyć, że działa zgodnie z przeznaczeniem, więc zainicjowanie go w deklaracji nie będzie problemem.

Ale warto, pracuję na dość dużej, kilkuletniej bazie kodu stworzonej przez TDD. Zwykle inicjalizujemy rzeczy w ich deklaracjach w kodzie testowym, a przez półtora roku, kiedy byłem nad tym projektem, nigdy nie spowodowało to problemu. Tak więc istnieją przynajmniej anegdotyczne dowody na to, że jest to rozsądne rozwiązanie.


45

Zacząłem kopać sam i znalazłem jedną potencjalną zaletę używania setUp(). Jeśli podczas wykonywania programusetUp() zostaną zgłoszone , JUnit wydrukuje bardzo pomocny ślad stosu. Z drugiej strony, jeśli wyjątek zostanie zgłoszony podczas tworzenia obiektu, komunikat o błędzie po prostu mówi, że JUnit nie był w stanie utworzyć instancji przypadku testowego i nie widzisz numeru wiersza, w którym wystąpił błąd, prawdopodobnie dlatego, że JUnit używa odbicia do utworzenia wystąpienia testu zajęcia.

Nic z tego nie dotyczy przykładu tworzenia pustej kolekcji, ponieważ to nigdy się nie wyrzuci, ale jest to zaleta setUp()metody.


18

Oprócz odpowiedzi Alexa B.

Wymagane jest nawet użycie metody setUp w celu utworzenia wystąpienia zasobów w określonym stanie. Wykonanie tego w konstruktorze jest nie tylko kwestią czasu, ale ze względu na sposób, w jaki JUnit przeprowadza testy, każdy stan testowy zostałby usunięty po uruchomieniu.

JUnit najpierw tworzy wystąpienia klasy testClass dla każdej metody testowej i rozpoczyna wykonywanie testów po utworzeniu każdej instancji. Przed uruchomieniem metody testowej uruchamiana jest jej metoda konfiguracji, w której można przygotować pewien stan.

Gdyby stan bazy danych został utworzony w konstruktorze, wszystkie instancje utworzyłyby stan bazy danych bezpośrednio po sobie, przed uruchomieniem każdego testu. Od drugiego testu testy będą działać w stanie brudnym.

Cykl życia JUnits:

  1. Utwórz inną instancję klasy testowej dla każdej metody testowej
  2. Powtórz dla każdej instancji klasy testclass: wywołaj setup + wywołaj metodę testmethod

Przy niektórych logach w teście z dwiema metodami testowymi otrzymujesz: (liczba to kod skrótu)

  • Tworzenie nowej instancji: 5718203
  • Tworzenie nowej instancji: 5947506
  • Konfiguracja: 5718203
  • TestOne: 5718203
  • Konfiguracja: 5947506
  • TestTwo: 5947506

3
Prawidłowo, ale poza tematem. Baza danych jest zasadniczo stanem globalnym. To nie jest problem, z którym się spotykam. Martwię się tylko o szybkość wykonywania odpowiednio niezależnych testów.
Craig P. Motlin

Ta kolejność inicjalizacji jest prawdziwa tylko w JUnit 3, gdzie jest ważnym ostrzeżeniem. W JUnit 4 instancje testowe są tworzone leniwie, więc inicjowanie w deklaracji lub w metodzie konfiguracji odbywa się w czasie testu. Również do jednorazowej konfiguracji można użyć @BeforeClassw JUnit 4.
Nils von Barth

11

W JUnit 4:

  • W przypadku testowanej klasy zainicjuj w @Beforemetodzie, aby wychwycić awarie.
  • W przypadku innych klas zainicjuj w deklaracji ...
    • ... dla zwięzłości i zaznaczenia pól final, dokładnie tak, jak podano w pytaniu,
    • ... chyba że jest to złożona inicjalizacja, która może się nie powieść, w takim przypadku użyj @Before, aby złapać awarie.
  • W przypadku stanu globalnego (szczególnie powolnej inicjalizacji , takiej jak baza danych), użyj @BeforeClass, ale bądź ostrożny na zależności między testami.
  • Inicjalizacja obiektu używanego w pojedynczym teście powinna oczywiście odbywać się w samej metodzie testowej.

Inicjalizacja w @Beforemetodzie lub metodzie testowej pozwala uzyskać lepsze raportowanie błędów w przypadku awarii. Jest to szczególnie przydatne do tworzenia instancji Class Under Test (którą można zepsuć), ale jest również przydatne do wywoływania systemów zewnętrznych, takich jak dostęp do systemu plików („nie znaleziono pliku”) lub połączenie z bazą danych („odmowa połączenia”).

Jest to dopuszczalne , aby mieć standard prosty i zawsze używaj@Before (oczywiste błędy, ale gadatliwy) lub zawsze inicjować w deklaracji (zwięzły, ale daje błędy mylące), ponieważ skomplikowane zasady kodowania są trudne do naśladowania, a to nie jest wielka sprawa.

Inicjalizacja w programie setUpjest reliktem JUnit 3, w którym wszystkie instancje testowe były chętnie inicjowane, co powoduje problemy (szybkość, pamięć, wyczerpanie zasobów) w przypadku kosztownej inicjalizacji. Dlatego najlepszą praktyką było wykonanie kosztownej inicjalizacji w programie setUp, która była uruchamiana dopiero po wykonaniu testu. To już nie obowiązuje, więc jest znacznie mniej konieczne setUp.

To podsumowuje kilka innych odpowiedzi, które pogrzebują lede, zwłaszcza Craiga P. Motlina (samo pytanie i odpowiedź własna), Moss Collum (klasa poddawana testowi) i dsaff.


7

W JUnit 3 inicjatory pól będą uruchamiane raz na metodę testową przed uruchomieniem jakichkolwiek testów . Tak długo, jak wartości pól są małe w pamięci, zajmują niewiele czasu na konfigurację i nie wpływają na stan globalny, używanie inicjatorów pól jest technicznie w porządku. Jednakże, jeśli to się nie uda, może skończyć się zużyciem dużej ilości pamięci lub czasu na konfigurację pól przed uruchomieniem pierwszego testu, a nawet może zabraknąć pamięci. Z tego powodu wielu programistów zawsze ustawia wartości pól w metodzie setUp (), gdzie jest to zawsze bezpieczne, nawet jeśli nie jest to bezwzględnie konieczne.

Zauważ, że w JUnit 4 inicjalizacja obiektu testowego ma miejsce tuż przed uruchomieniem testu, więc używanie inicjatorów pól jest bezpieczniejsze i zalecane.


Ciekawy. Czyli zachowanie, które opisałeś na początku, dotyczy tylko JUnit 3?
Craig P. Motlin

6

W Twoim przypadku (tworzenie listy) nie ma różnicy w praktyce. Ale generalnie lepiej jest użyć setUp (), ponieważ pomoże to Junitowi poprawnie zgłosić wyjątki. Jeśli w konstruktorze / inicjatorze testu wystąpi wyjątek, oznacza to niepowodzenie testu . Jeśli jednak podczas konfiguracji wystąpi wyjątek, naturalne jest myślenie o tym jako o problemie podczas konfigurowania testu, a junit odpowiednio to zgłasza.


1
dobrze powiedziane. Po prostu przyzwyczaj się do zawsze tworzenia instancji w setUp () i masz jedno pytanie mniej do zmartwień - np. Gdzie powinienem utworzyć instancję mojego fooBar, gdzie moja kolekcja. To rodzaj standardu kodowania, do którego wystarczy się zastosować. Korzyści nie z listami, ale z innymi instancjami.
Olaf Kock

@Olaf Dzięki za informacje o standardzie kodowania, nie myślałem o tym. Jednak bardziej zgadzam się z koncepcją standardu kodowania Mossa Colluma.
Craig P. Motlin

5

Preferuję najpierw czytelność, która najczęściej nie korzysta z metody konfiguracji. Robię wyjątek, gdy podstawowa operacja konfiguracji zajmuje dużo czasu i jest powtarzana w każdym teście.
W tym momencie przenoszę tę funkcjonalność do metody konfiguracji za pomocą @BeforeClassadnotacji (optymalizuję później).

Przykład optymalizacji za pomocą @BeforeClassmetody konfiguracji: używam dbunit do niektórych testów funkcjonalnych baz danych. Metoda konfiguracji odpowiada za wprowadzenie bazy danych w znany stan (bardzo wolny ... 30 sekund - 2 minuty w zależności od ilości danych). Ładuję te dane w metodzie konfiguracji z adnotacją, @BeforeClassa następnie uruchamiam 10-20 testów dla tego samego zestawu danych, w przeciwieństwie do ponownego ładowania / inicjowania bazy danych w każdym teście.

Korzystanie z Junit 3.8 (rozszerzenie TestCase, jak pokazano w przykładzie) wymaga napisania nieco więcej kodu niż tylko dodanie adnotacji, ale „uruchom raz przed konfiguracją klasy” jest nadal możliwe.


1
+1, bo ja też wolę czytelność. Nie jestem jednak przekonany, że drugi sposób to w ogóle optymalizacja.
Craig P. Motlin

@Motlin Dodałem przykład dbunit, aby wyjaśnić, w jaki sposób można zoptymalizować konfigurację.
Alex B

Baza danych jest zasadniczo stanem globalnym. Dlatego przeniesienie konfiguracji db do setUp () nie jest optymalizacją, jest konieczne, aby testy zakończyły się poprawnie.
Craig P. Motlin

@Alex B: Jak powiedział Motlin, to nie jest optymalizacja. Zmieniasz tylko miejsce w kodzie, w którym odbywa się inicjalizacja, ale nie ile razy ani jak szybko.
Eddie,

Zamierzałem zasugerować użycie adnotacji „@BeforeClass”. Edycja przykładu w celu wyjaśnienia.
Alex B

2

Ponieważ każdy test jest wykonywany niezależnie, z nową instancją obiektu, nie ma większego sensu, aby obiekt Test miał jakikolwiek stan wewnętrzny oprócz tego, który jest dzielony między setUp()i pojedynczym testem i tearDown(). To jeden z powodów (oprócz powodów podanych przez innych), dla których dobrze jest stosować tę setUp()metodę.

Uwaga: to zły pomysł, aby obiekt testowy JUnit utrzymywał stan statyczny! Jeśli używasz zmiennej statycznej w swoich testach do celów innych niż śledzenie lub diagnostyka, unieważniasz część celu JUnit, który polega na tym, że testy mogą (lub mogą) być uruchamiane w dowolnej kolejności, każdy test uruchomiony z świeży, czysty stan.

Zaletą używania setUp()jest to, że nie musisz wycinać i wklejać kodu inicjującego w każdej metodzie testowej i że nie masz kodu konfiguracji testu w konstruktorze. W twoim przypadku różnica jest niewielka. Samo utworzenie pustej listy można bezpiecznie wykonać podczas jej wyświetlania lub w konstruktorze, ponieważ jest to trywialna inicjalizacja. Jednak, jak zauważyliście ty i inni, wszystko, co może wrzucić, Exceptionpowinno być zrobione setUp(), aby w razie niepowodzenia otrzymać zrzut stosu diagnostycznego.

W twoim przypadku, gdy tworzysz tylko pustą listę, postąpiłbym tak samo, jak sugerujesz: Przypisz nową listę w miejscu deklaracji. Zwłaszcza, że ​​w ten sposób masz możliwość zaznaczenia go, finaljeśli ma to sens dla twojej klasy testowej.


1
+1, ponieważ jesteś pierwszą osobą, która faktycznie wspiera inicjalizację listy podczas budowy obiektu, aby oznaczyć ją jako ostateczną. Kwestia dotycząca zmiennych statycznych jest jednak poza tematem.
Craig P. Motlin

@Motlin: prawda, rzeczy o zmiennych statycznych są trochę nie na temat. Nie jestem pewien, dlaczego to dodałem, ale w tamtym czasie wydawało się to stosowne, jako rozszerzenie tego, co mówiłem w pierwszym akapicie.
Eddie

Jednak finalw pytaniu wspomniano o zaletach .
Nils von Barth

0
  • Stałe wartości (używane w urządzeniach lub asercjach) powinny być inicjowane w ich deklaracjach i final(ponieważ nigdy się nie zmieniają)

  • testowany obiekt powinien zostać zainicjowany w metodzie konfiguracji, ponieważ możemy ustawić coś na. Oczywiście możemy nie ustawić czegoś teraz, ale możemy to ustawić później. Utworzenie wystąpienia w metodzie init ułatwiłoby zmiany.

  • zależności testowanego obiektu, jeśli są one wyszydzane, nie powinny być nawet tworzone samodzielnie: obecnie makiety frameworki mogą utworzyć instancję poprzez odbicie.

Test bez zależności od makiety mógłby wyglądać następująco:

public class SomeTest {

    Some some; //instance under test
    static final String GENERIC_ID = "123";
    static final String PREFIX_URL_WS = "http://foo.com/ws";

    @Before
    public void beforeEach() {
       some = new Some(new Foo(), new Bar());
    } 

    @Test
    public void populateList()
         ...
    }
}

Test z zależnościami do wyizolowania mógłby wyglądać następująco:

@RunWith(org.mockito.runners.MockitoJUnitRunner.class)
public class SomeTest {

    Some some; //instance under test
    static final String GENERIC_ID = "123";
    static final String PREFIX_URL_WS = "http://foo.com/ws";

    @Mock
    Foo fooMock;

    @Mock
    Bar barMock;

    @Before
    public void beforeEach() {
       some = new Some(fooMock, barMock);
    }

    @Test
    public void populateList()
         ...
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.