@BeforeClass i inheritance - kolejność wykonywania

93

Mam abstrakcyjną klasę bazową, której używam jako bazy dla moich testów jednostkowych (TestNG 5.10). W tej klasie inicjalizuję całe środowisko dla moich testów, konfiguruję mapowania bazy danych itp. Ta abstrakcyjna klasa posiada metodę z @BeforeClassadnotacją, która wykonuje inicjalizację.

Następnie rozszerzam tę klasę o konkretne klasy, w których mam @Testmetody, a także @BeforeClassmetody. Metody te wykonują specyficzną dla klasy inicjalizację środowiska (np. Umieszczają niektóre rekordy w bazie danych).

Jak mogę wymusić określoną kolejność @BeforeClassmetod z adnotacjami? Potrzebuję, aby te z abstrakcyjnej klasy bazowej były wykonywane przed tymi z klasy rozszerzającej.

Przykład:

abstract class A {
    @BeforeClass
    doInitialization() {...}
}

class B extends A {
    @BeforeClass
    doSpecificInitialization() {...}

    @Test
    doTests() {...}
}

Oczekiwane zamówienie:

A.doInitialization
B.doSpecificInitialization
B.doTests

Rzeczywiste zamówienie:

B.doSpecificInitialization // <- crashes, as the base init is missing
(A.doInitialization        // <---not executed
 B.doTests)                // <-/
Dominik Sandjaja
źródło

Odpowiedzi:

50

Nie rób tego @BeforeClassw abstractklasie. Wywołaj to z każdej podklasy.

abstract class A {
    void doInitialization() {}
}

class B extends A {
    @BeforeClass
    void doSpecificInitialization() {
        super.doInitialization();
    }

    @Test
    void doTests() {}
}

Wygląda na to, że TestNG ma @BeforeClass(dependsOnMethods={"doInitialization"})- spróbuj.

Bozho
źródło
10
W zasadzie właśnie tego chciałem uniknąć: nie ma potrzeby jawnego wywoływania metod klasy super (abstrakcyjnej). Zwłaszcza, że ​​mam też klasy, które dziedziczą po A, ale nie mają własnej metody @BeforeClass. Musiałbym wstawić jeden tylko w tym celu.
Dominik Sandjaja,
5
dependsOnMethodsObejście załatwiło sprawę. Chociaż wolałbym podejście "najpierw superklasy" ...
Dominik Sandjaja
1
Aby użyć „dependsOnMethod”, czy „doInitialization” nie powinno być opatrzone adnotacją „@Test”? To jest problem, ponieważ technicznie nie jest to test sam w sobie ...
N3da
@BeforeClass powinno zawierać adnotacje dotyczące metody statycznej
Fabrizio Stellato,
110

edycja: Odpowiedź poniżej jest dla JUnit , ale i tak ją tutaj zostawię, ponieważ może być pomocna.

Zgodnie z api JUnit : "Metody @BeforeClass nadklas będą uruchamiane przed metodami bieżącej klasy."

Przetestowałem to i wydaje mi się, że działa.

Jednak, jak wspomina @Odys poniżej, dla JUnit musisz mieć dwie metody nazwane inaczej, chociaż inaczej spowoduje to, że uruchomiona zostanie tylko metoda podklasy, ponieważ element nadrzędny zostanie przesłany w cień.

Fortega
źródło
56
Mimo że pierwotne pytanie dotyczyło TestNG, dotarłem tutaj po wygooglowaniu dla JUnit i Twoja odpowiedź pomogła - dzięki!
teabot
10
w przypadku JUnit musisz mieć dwie metody nazwane inaczej, ponieważ w przeciwnym razie zostanie uruchomiona tylko metoda podklasy, ponieważ element nadrzędny zostanie przesłany.
Odys
3
@Odys, bardzo dziękuję za wspomnienie o tym. Starałem się zrozumieć, dlaczego metoda „setup” w mojej podklasie działa, podczas gdy ta w jej nadklasie nie. Uratowałeś mi masę irytacji!
Tom Catullo
Uczyniłeś mój dzień. Dzięki!
raiks
7

Dodałem publicdo klasy abstrakcyjnej i TestNG (6.0.1) wykonałem wcześniej doInitialization () doTests. TestNG nie wykonuje się, doInitialization()jeśli usunę publicz klasy A.

public abstract class A {
 @BeforeClass
 doInitialization() {...}
}

class B extends A {    
 @Test
 doTests() {...}
}
Paweł
źródło
1
To prawda, ale nie ma to znaczenia. Nie działa to, gdy klasa ma B również@BeforeClass metodę z adnotacjami, jak w przypadku OP.
jpaugh
1
Zrobiłem to samo. Wydaje się, że kolejność dziedziczenia jest pomijana, jeśli metoda podstawowa jest prywatna. Dzięki!
Manu
6

Właśnie wypróbowałem twój przykład z 5.11 i otrzymuję @BeforeClass klasy bazowej wywołanej jako pierwsza.

Czy możesz opublikować swój plik testng.xml? Być może określasz tam zarówno A, jak i B, podczas gdy konieczne jest tylko B.

Zapraszam do śledzenia na liście mailingowej testng-users, a my przyjrzymy się bliżej twojemu problemowi.

- Cedric

Cedric Beust
źródło
2
Brak .xml dla zdefiniowanego testng (jawnie), jest uruchamiany z Eclipse i Maven.
Dominik Sandjaja
Jak dokładnie to robisz z Eclipse? Kliknięcie prawym przyciskiem myszy na klasę B?
Cedric Beust
4

Właśnie przez to przeszedłem i znalazłem jeszcze jeden sposób, aby to osiągnąć. Po prostu użyj alwaysRunna @BeforeClasslub @BeforeMethodw klasie abstrakcyjnej, działa zgodnie z oczekiwaniami.

public class AbstractTestClass {
    @BeforeClass(alwaysRun = true)
    public void generalBeforeClass() {
        // do stuff
        specificBeforeClass();
    }
}
Konrad Garus
źródło
2

Kiedy biegnę z: JUnitCore.runClasses (TestClass.class); Wykona poprawnie rodzica przed dzieckiem (nie potrzebujesz super.SetUpBeforeClass (); ) Jeśli uruchomisz go z Eclipse: Z jakiegoś powodu nie udaje mu się uruchomić klasy bazowej. Sposób obejścia: Wyraźnie wywołaj klasę bazową: ( BaseTest.setUpBeforeClass (); ) Możesz chcieć mieć flagę w klasie bazowej na wypadek, gdybyś uruchomiła ją z aplikacji, aby określić, czy jest już skonfigurowana, czy nie. Więc działa tylko raz, jeśli uruchomisz go za pomocą obu możliwych metod (np. Z eclipse do testów osobistych i przez ANT w przypadku wydania kompilacji).

Wygląda na to, że jest to błąd w Eclipse lub przynajmniej nieoczekiwane wyniki.

Steve
źródło
2

Dla JUnit : Jak wspomniał @fortega: Zgodnie z api JUnit: "Metody @BeforeClass nadklas będą uruchamiane przed metodami bieżącej klasy."

Uważaj jednak, aby nie nazwać obu metod tą samą nazwą . Ponieważ w tym przypadku metoda nadrzędna zostanie ukryta przez rodzica-dziecko. Źródło .

Anatolii Stepaniuk
źródło
1

Co powiesz na wywołanie przez metodę @BeforeClass pustej metody specificBeforeClass (), która może, ale nie musi, zostać nadpisana przez podklasy, takie jak ta:

public class AbstractTestClass {
  @BeforeClass
  public void generalBeforeClass() {
    // do stuff
    specificBeforeClass();
  }

  protected void specificBeforeClass() {}
}

public class SpecificTest {
  @Override
  protected void specificBeforeClass() {
    // Do specific stuff
  }

  // Tests
}
Tatome
źródło
3
BeforeClass musi być statyczny, więc nie możesz tego zrobić z junit
madx
1

dependsOnMethod może być użyte.

np. w przypadku wiosny ( AbstractTestNGSpringContextTests)

@BeforeClass(alwaysRun = true, dependsOnMethods = "springTestContextPrepareTestInstance")
Sandeep Jindal
źródło
1

Sprawdź wyciąg z importu. Powinno być

import org.testng.annotations.BeforeClass;

nie

import org.junit.BeforeClass;

nishantrevo
źródło
1

To działa dla mnie -

abstract class A {
    @BeforeClass
    doInitialization() {...}
}

class B extends A {
    @Override
    @BeforeClass
    doInitialization() { 

       //do class specific init

    }   

    @Test
    doTests() {...}
}
srikar
źródło
1
Dodaj krótkie wyjaśnienie, co robi ten kod, i odpowie na pierwotne pytanie
Cray
0

Dlaczego nie spróbujesz utworzyć abstrakcyjnej metody doSpecialInit () w swojej superklasie, wywoływanej z Twojej metody z adnotacjami BeforeClass w nadklasie.

Dlatego programiści dziedziczący twoją klasę są zmuszeni do implementacji tej metody.

Gra w kości
źródło
Szczerze mówiąc, nawet logika mogła się zmienić w ciągu ostatnich 3 i pół roku, odkąd zadałem to pytanie ... ;-) Więc tak, może to był pomysł, może nie zadziałał - szczerze mówiąc nie Zapamiętaj.
Dominik Sandjaja,
0

Jest tu inne proste rozwiązanie.

Moja szczególna sytuacja polega na tym, że muszę wstrzyknąć usługi pozorowane z klasy „BeforeClass” w podklasie przed wykonaniem elementu „BeforeClass” w nadklasie.

Aby to zrobić - po prostu użyj @ClassRulew podklasie.

Na przykład:

@ClassRule
public static ExternalResource mocksInjector = new ExternalResource() {
    @Override
    protected void before() {
        // inject my mock services here
        // Note: this is executed before the parent class @BeforeClass
    }
};

Mam nadzieję, że to pomoże. Może to skutecznie wykonać statyczną konfigurację w „odwrotnej” kolejności.

vikingsteve
źródło
0

Dzisiaj miałem podobny problem, jedyną różnicą było to, że klasa bazowa nie była abstrakcyjna

Oto moja sprawa

public class A {
    @BeforeClass
    private void doInitialization() {...}
}

public class B extends A {
    @BeforeClass
    private void doSpecificInitialization() {...}

    @Test
    public void doTests() {...}
}

Okazało się, że @BeforeClassmetoda z klasy A nigdy nie została wykonana.

  • A.doInitialization () -> TO NIGDY NIE BYŁO WYKONYWANE CICHO
  • B.doSpecificInitialization ()
  • B.doTests ()

Gry z modyfikatorów prywatności stwierdziliśmy, że TestNG nie wykona się @BeforeClassuwagami z odziedziczonej metody klasy , jeśli metoda nie jest widoczny z klasy-spadkobierca

Więc to zadziała:

public class A {
    @BeforeClass
    private void doInitialization() {...}
}

public class B extends A {
    @BeforeClass
    //Here a privacy modifier matters -> please make sure your method is public or protected so it will be visible for ancestors
    protected void doSpecificInitialization() {...}

    @Test
    public void doTests() {...}
}

W rezultacie następuje:

  • A.doInitialization ()
  • B.doSpecificInitialization ()
  • B.doTests ()
rodikno
źródło
-1

W moim przypadku (JUnit) mam te same metody o nazwie setup () w klasie bazowej i klasie pochodnej. W tym przypadku tylko metoda klasy pochodnej nazywa, i mam go wywołać metodę klasy bazowej.

Mike Sokolov
źródło
-2

Lepszym i czystszym sposobem osiągnięcia tego za pomocą dziedziczenia może być:

abstract class A {

    @BeforeClass
    void doInitialization() {}
}

class B extends A {

    @Override
    @BeforeClass
    void doInitialization() {
        super.doInitialization();
    }

    @Test
    void doTests() {}
}
kushal
źródło
1
Jeśli chcesz, aby metoda w klasie Parent była wykonywana jako pierwsza, wystarczy, że nazwij metodę w klasie Child inaczej niż w klasie Parent (ponieważ jeśli mają ten sam podpis, to polimorfizm przychodzi w akcji). Uważam, że to czystszy sposób.
Anatolii Stepaniuk,