@BeforeClass і спадкування - порядок виконання


90

У мене є абстрактний базовий клас, який я використовую як основу для своїх модульних тестів (TestNG 5.10). У цьому класі я ініціалізую все середовище для своїх тестів, налаштування зіставлення баз даних тощо. Цей абстрактний клас має метод з @BeforeClassанотацією, який виконує ініціалізацію.

Далі я розширюю цей клас конкретними класами, в яких я маю @Testметоди, а також @BeforeClassметоди. Ці методи виконують специфічну для класу ініціалізацію середовища (наприклад, додають деякі записи до бази даних).

Як я можу застосувати певний порядок @BeforeClassанотованих методів? Мені потрібні ті з абстрактного базового класу, які будуть виконані перед класами, що розширюються.

Приклад:

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

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

    @Test
    doTests() {...}
}

Очікуване замовлення:

A.doInitialization
B.doSpecificInitialization
B.doTests

Фактичне замовлення:

B.doSpecificInitialization // <- crashes, as the base init is missing
(A.doInitialization        // <---not executed
 B.doTests)                // <-/

Відповіді:


50

Не кладіть @BeforeClassна abstractклас. Викличте його з кожного підкласу.

abstract class A {
    void doInitialization() {}
}

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

    @Test
    void doTests() {}
}

Здається, у TestNG є @BeforeClass(dependsOnMethods={"doInitialization"})- спробуйте.


9
В основному цього я хотів уникнути: немає необхідності явно викликати методи супер (абстрактного) класу. Тим більше, що у мене також є класи, які успадковуються від A, але не мають власного методу @BeforeClass. Мені довелося б вставити одну лише для цієї мети.
Домінік Санджая

5
dependsOnMethodsОбхідний шлях зробив трюк. Хоча я віддав би перевагу підходу "спочатку суперкласу" ...
Домінік Санджая

1
Щоб використовувати "dependOnMethod", чи не слід "doInitialization" позначати "@Test"? Це проблема, оскільки технічно це не тест сам по собі ...
N3da

@BeforeClass повинен анотувати статичний метод
Fabrizio Stellato

108

редагувати: Відповідь нижче стосується JUnit , але я все одно залишу його тут, оскільки це може бути корисним.

Згідно з api JUnit : "Методи @BeforeClass суперкласів будуть виконуватися раніше тих, що є в поточному класі."

Я перевірив це, і, здається, це працює для мене.

Однак, як @Odys згадує нижче, для JUnit вам потрібно мати два методи, іменовані по-різному, хоча, якщо це зробити інакше, результатом буде лише запуск методу підкласу, оскільки батько буде затінений.


56
Незважаючи на те, що початкове запитання стосувалось TestNG, я приїхав сюди після гуглу для JUnit, і ваша відповідь допомогла - спасибі!
teabot

9
для JUnit вам потрібно мати два методи, іменовані по-різному, хоча, якщо це зробити інакше, результатом буде лише метод підкласу, який буде запущений, оскільки батько буде затінений.
Одіс

2
@Odys, велике спасибі, що згадали про це. Я намагався зрозуміти, чому метод "setup" у моєму підкласі був запущений, тоді як метод у його суперкласі не працював. Ви щойно врятували мені тонну загострення!
Том Катулло,

Ти зробив мій день. Дякую!
raiks

7

Я додав publicдо абстрактного класу і TestNG (6.0.1) виконував doInitialization () раніше doTests. TestNG не виконується, doInitialization()якщо я видаляю publicз класу А.

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

class B extends A {    
 @Test
 doTests() {...}
}

1
Це правда, але не має значення. Це не працює, коли клас B також має @BeforeClass-нотований метод, як у випадку з OP.
jpaugh

1
Я зробив те саме. Здається, порядок успадкування пропущений, якщо базовий метод приватний. Дякую!
Ману

6

Я щойно спробував ваш приклад з 5.11 і спочатку отримую @BeforeClass базового класу.

Чи можете ви опублікувати файл testng.xml? Можливо, ви вказуєте там і А, і В, тоді як необхідний лише В.

Не соромтеся стежити за списком розсилки testng-users, і ми можемо детальніше розглянути вашу проблему.

- Седрік


2
Жодного .xml для testng не визначено (явно), він запускається з Eclipse та Maven.
Домінік Санджая

Як ви точно запускаєте його з Eclipse? Клацніть правою кнопкою миші на класі B?
Cedric Beust

4

Я щойно пройшов це і знайшов ще один спосіб досягти цього. Просто використовуйте alwaysRunна @BeforeClassабо @BeforeMethodв абстрактному класі, працює так, як ви очікували.

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

2

Коли я запускаю з: JUnitCore.runClasses (TestClass.class); Він буде правильно виконувати батьківський, дочірній матеріал (Вам не потрібен super.SetUpBeforeClass (); ) Якщо ви запускаєте його з Eclipse: чомусь не вдається запустити базовий клас. Подолання : Викличте базовий клас явно: ( BaseTest.setUpBeforeClass (); ) Можливо, ви захочете мати прапорець у базовому класі, якщо ви запускаєте його із програми, щоб визначити, чи він уже встановлений чи ні. Тож він запускається лише один раз, якщо ви запускаєте його за допомогою обох можливих методів (наприклад, від eclipse для особистого тестування та через ANT для випуску збірки).

Здається, це помилка з Eclipse або принаймні несподівані результати.


2

Для JUnit : Як зазначав @fortega: Відповідно до api JUnit: "Методи @BeforeClass суперкласів будуть виконуватися раніше тих, що є в поточному класі."

Але будьте обережні, щоб не називати обидва методи однаковою назвою . Оскільки в цьому випадку батьківський метод буде прихований дочірнім батьком. Джерело .


1

Як щодо того, щоб ваш метод @BeforeClass викликав порожній метод specificBeforeClass (), який може або не може бути замінений підкласами приблизно так:

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

  protected void specificBeforeClass() {}
}

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

  // Tests
}

3
BeforeClass повинен бути статичним, щоб ви не могли цього зробити за допомогою
junit

1

dependsOnMethod може бути використаний.

наприклад, у випадку весни ( AbstractTestNGSpringContextTests)

@BeforeClass(alwaysRun = true, dependsOnMethods = "springTestContextPrepareTestInstance")

1

Перевірте свою заяву про імпорт. Вона повинна бути

import org.testng.annotations.BeforeClass;

ні

import org.junit.BeforeClass;


1

Це працює для мене -

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

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

       //do class specific init

    }   

    @Test
    doTests() {...}
}

1
Додайте коротке пояснення того, що робить цей код, і
Крей

0

Чому б вам не спробувати створити абстрактний метод doSpecialInit () у своєму суперкласі, викликаний з вашого анотованого методу BeforeClass у суперкласі.

Тож розробники, які успадковують ваш клас, змушені реалізовувати цей метод.


Чесно кажучи, навіть логіка, можливо, змінилася за останні 3 1/2 роки, відколи я задав це запитання ... ;-) Так що так, можливо, це була ідея, можливо, це не спрацювало - я, чесно кажучи, не пам’ятай.
Домінік Санджая,

0

Тут є ще одне просте рішення.

Моя особлива ситуація полягає в тому, що мені потрібно внести фіктивні служби з "BeforeClass" у підклас до того, як буде виконано "BeforeClass" у суперкласі.

Для цього просто використовуйте a @ClassRuleу підкласі.

Наприклад:

@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
    }
};

Сподіваюся, це допоможе. Це може ефективно виконувати статичне налаштування в "зворотному" порядку.


0

Я зіткнувся з подібною проблемою сьогодні, єдина відмінність - базовий клас не був абстрактним

Ось мій випадок

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

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

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

Сталося так, що @BeforeClassметод із класу А ніколи не виконувався.

  • A.doInitialization () -> ЦЕ НІКОЛИ НЕ ВИКОНАВАЛОСЯ мовчки
  • B.doSpecificInitialization ()
  • B.doTests ()

Граючи з модифікаторами секретності я виявив , що TestNG не виконуватиметься в @BeforeClassанотований метод з успадкованого класу , якщо метод не видно з класу-спадкодавця

Отже, це буде працювати:

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() {...}
}

В результаті відбувається наступне:

  • A.doInitialization ()
  • B.doSpecificInitialization ()
  • B.doTests ()

-1

У моєму випадку (JUnit) у мене однакові методи, що називаються setup () у базовому класі та похідному класі. У цьому випадку викликається лише метод похідного класу, і я викликаю метод базового класу.


-2

Кращим і чистішим способом досягнення цього за допомогою спадкування може бути наступний -

abstract class A {

    @BeforeClass
    void doInitialization() {}
}

class B extends A {

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

    @Test
    void doTests() {}
}

1
Якщо вам потрібно спочатку виконати метод у батьківському класі, вам просто потрібно назвати метод у дочірньому класі інакше, ніж батьківський (тому що якщо вони мають однаковий підпис, тоді вступає в дію поліморфізм). Я вважаю, що це чистіший спосіб.
Анатолій Степанюк
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.