Junit przed zajęciami (niestatyczna)

84

Czy są jakieś najlepsze praktyki, aby Junit wykonywał funkcję raz w pliku testowym, a także nie powinien być statyczny?

jak @BeforeClassna funkcji statycznej?

Oto brzydkie rozwiązanie:

@Before void init(){
    if (init.get() == false){
        init.set(true);
        // do once block
    }
}

cóż, to jest coś, czego nie chcę robić i szukam zintegrowanego rozwiązania junit.

rzymski
źródło
Cóż, mam dość dużą hierarchię plików testowych i podstawowych plików testowych, potrzebuję możliwości przesłonięcia tej akcji w klasach testów potomnych.
Roman
1
Miałem ten sam problem, w którym tylko pierwszy z wielu sparametryzowanych testów powinien wykonać logowanie.
dokaspar
5
Zauważ, że "brzydkie" rozwiązanie, które działa ze zwykłym JUnitem, nie bierze pod uwagę testów zrywających.
eskatos

Odpowiedzi:

22

Jeśli nie chcesz konfigurować statycznych inicjatorów do jednorazowej inicjalizacji i nie zależy Ci na używaniu JUnit, spójrz na TestNG. TestNG obsługuje niestatyczną, jednorazową inicjalizację z różnymi opcjami konfiguracji, wszystkie przy użyciu adnotacji.

W TestNG byłoby to równoważne z:

@org.testng.annotations.BeforeClass
public void setUpOnce() {
   // One time initialization.
}

Do porzucenia,

@org.testng.annotations.AfterClass
public void tearDownOnce() {
   // One time tear down.
}

Dla odpowiednika TestNG JUnit 4's @Beforeand @After, możesz użyć odpowiednio @BeforeMethodi @AfterMethod.

Kartik
źródło
41

Proste stwierdzenie if wydaje się też działać całkiem nieźle:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:test-context.xml"})
public class myTest {

    public static boolean dbInit = false;

    @Autowired
    DbUtils dbUtils;

    @Before
    public void setUp(){

        if(!dbInit){

            dbUtils.dropTables();
            dbUtils.createTables();
            dbInit = true;

        }
    }

 ...
Upgradingdave
źródło
1
Ładnie i prosto! Ale czy nie widzisz sposobu na prostą adaptację tego, aby uzyskać niestatyczny @AfterClassodpowiednik, który zrywa się po zakończeniu wszystkich testów?
Steve Chambers
1
Zobacz tutaj, aby uzyskać aktualizację tej metody, która powinna działać dla klas testowych, które używają dziedziczenia.
Steve Chambers
36

Najłatwiejszym rozwiązaniem jest użycie pustego konstruktora. Nadal można przesłonić konstruktora w klasie rozszerzonej.

Ale to nie jest optymalne z całym dziedzictwem. Dlatego JUnit 4 używa zamiast tego adnotacji.

Inną opcją jest utworzenie metody pomocniczej w klasie factory / util i pozwolenie tej metodzie wykonać całą pracę.

Jeśli używasz Spring, powinieneś rozważyć użycie @TestExecutionListenersadnotacji. Coś takiego jak ten test:

@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({CustomTestExecutionListener.class, 
     DependencyInjectionTestExecutionListener.class})
@ContextConfiguration("test-config.xml")
public class DemoTest {

Spring AbstractTestExecutionListenerzawiera na przykład tę pustą metodę, którą możesz nadpisać:

public void beforeTestClass(TestContext testContext) throws Exception {
    /* no-op */
}

UWAGA: NIE przeoczyć / przegapić DependencyInjectionTestExecutionListenerpodczas dodawania niestandardowego TestExecutionListeners. Jeśli to zrobisz, wszystkie autoprzewodniki będą null.

Espen
źródło
+1 Ta technika rozwiązała mój problem, gdy chciałem używać DbUnit i ładować zbiór danych tylko raz na klasę
Brad
+1 To jest idealne ... dla ludzi, którzy nie są przywiązani do starożytnej wersji wiosny. :(
Mike Miller
1
Czy zostanie to beforeTestClass()wywołane przed czy po zainicjowaniu kontekstu?
Dims
@Dims po zainicjowaniu kontekstu
Anand Rockzz
7

Z łatwością używaj @BeforeAllMethods/ @AfterAllMethodsadnotacji, aby uruchamiać metodę w kontekście wystąpienia (niestatycznym), w którym będą dostępne wszystkie wstrzyknięte wartości.

Jest do tego specjalna biblioteka testowa:

https://mvnrepository.com/artifact/org.bitbucket.radistao.test/before-after-spring-test-runner/0.1.0

https://bitbucket.org/radistao/before-after-spring-test-runner/

Jedyne ograniczenie: działa tylko w przypadku testów wiosennych .

(Jestem twórcą tej biblioteki testowej)

radistao
źródło
0

Nigdy nie próbowałem, ale może możesz utworzyć konstruktor bez argumentów i wywołać swoją funkcję z tego miejsca?

rzymski
źródło
To by zadziałało, problem polega na tym, że potrzebuję możliwości nadpisania tego działania w klasach, które rozszerzają tę podstawową klasę testową
Roman
@Roman: och, teraz rozumiem. Dodaj to do swojego posta, ten komentarz wyjaśni sprawę.
Roman
Konstruktor będzie wywoływany tyle razy, ile jest przypadków testowych. Dla każdej metody testowej zostanie utworzony nowy obiekt klasy Test. Tak więc użycie konstruktora nie jest tutaj rozwiązaniem
manikanta
Również to nie zadziała z iniekcją zależności, która opiera się na już skonstruowanym obiekcie.
Mike Miller,
0

W artykule omówiono 2 bardzo ładne rozwiązania tego problemu:

  1. „czysty” junit z niestandardowym Runnerem (używając interfejsu, ale możesz go rozszerzyć o niestandardową adnotację, np. @BeforeInstance)
  2. Słuchacze wiosennych egzekucji, jak wspomniał wcześniej Espen.
kedzi
źródło
0

AKTUALIZACJA: Zobacz komentarz Cherry, aby dowiedzieć się, dlaczego poniższa sugestia jest błędna. (Zostawiam odpowiedź tutaj, zamiast ją usuwać, ponieważ komentarz może dostarczyć innym przydatnych informacji, dlaczego to nie działa).


Inną opcją wartą rozważenia przy użyciu wstrzykiwania zależności (np. Spring) jest @PostConstruct. To zagwarantuje zakończenie iniekcji zależności, co nie miało miejsca w konstruktorze:

@PostConstruct
public void init() {
    // One-time initialization...
}

Steve Chambers
źródło
7
Bardzo złe rozwiązanie w przypadku testów Junit. Junit tworzy instancję klasy testowej za każdym razem, gdy uruchamia metodę testową. Więc jeśli w klasie jest 6 metod testowych, konstruktor klasy @Beforei @Aftermetody zostaną wywołane 6 razy! Więc w tym kontekście @PostConstructzachowuje się jak @Beforeadnotacja. Możesz to po prostu przetestować: po prostu umieść 2 metody testowe w klasie testowej, dodaj @PostConstruct public void init() {System.out.println("started");}i zobacz w dziennikach, ile razy jest drukowany.
Cherry
Aby uzyskać więcej informacji, właśnie natrafiłem na dokumentację JUnit, która potwierdza to, co zostało opisane w powyższym komentarzu na temat tworzenia instancji przez JUnit dla każdego @Testuruchomienia: „Aby uruchomić metodę, JUnit najpierw tworzy nową instancję klasy, a następnie wywołuje metodę z adnotacjami”.
Steve Chambers
-2

Po prostu użyj @BeforeClass:

@BeforeClass
public static void init() {
}

Nie ma sensu initbyć statycznym, ponieważ każdy test jest uruchamiany w oddzielnej instancji. Instancja, na której initjest uruchamiana, nie pasowałaby do instancji żadnego testu.

Jedynym powodem, dla którego możesz chcieć, aby nie był statyczny, jest przesłonięcie go w podklasach, ale możesz to również zrobić za pomocą metod statycznych. Po prostu użyj tej samej nazwy, a initzostanie wywołana tylko metoda podklasy .

fgb
źródło
2
Całe to pytanie dotyczy możliwości zrobienia tego w sposób niestatyczny, co jest potrzebne, jeśli potrzebujesz zmiennych instancji w klasie.
Simon Forsberg
@SimonForsberg Tak, i mówię, że jest to problem z XY. Opiekun powiedział, że problemem jest przesłonięcie zachowania w klasach dzieci. Jeśli przykład wymagał zmiennych instancji, mógłbym zasugerować coś innego.
fgb
Zobacz ten komentarz: stackoverflow.com/questions/2825615/…
Simon Forsberg
@SimonForsberg To jest komentarz, o którym mówiłem. Co z tym?
fgb